【问题标题】:Deep-copying a structure containing reference members (C++)深度复制包含引用成员的结构 (C++)
【发布时间】:2014-03-05 09:40:18
【问题描述】:

我有一个 C++ 类,其中包含具有指向类中其他对象的引用成员的对象。这在当时似乎是个好主意,但现在我需要实现整个事情的深层副本,而且我找不到一种方法来做这件事,感觉不像是笨拙的 hack。

我的代码的简化版本如下所示。问题是关于为A 编写一个复制构造函数。

class C {
    int x, y, z; // nothing complicated stored in this class
public:
    // constructor and other methods
};

class B {
    C &c1;
    C &c2;
public:
    // constructor and other methods
};

class A {
    C *c_array;
    B *b_array; // for each item in this array, 
                // its 'c1' and 'c2' fields point to members of c_array.
public:
    // constructor and other methods
};

有些人问这个结构是如何初始化的,所以请让我强调一下,这与回答问题无关。初始化将始终满足b_array 中项目的引用成员指向c_array 中的项目的要求,但除此之外,数据可以是任何东西。复制构造函数适用于满足此属性的任何数据是很重要的。这不是可以通过重用现有初始化代码来解决的问题。

问题是,如果我只是复制b_array 中的对象,它们的引用成员将指向Aold 实例中的C 对象。我需要让它们指向 new 实例中的相应项目。我能想到的唯一方法是:

  • 对于b_array的每个元素,获取其引用成员指向的地址,并将其存储在指针中

  • 使用指针算法计算出该指针对应的数组索引

  • 使用该索引初始化新b_array的对应元素的引用成员。

我的问题是,有没有更清洁/更简单/更优雅的方式?如果没有,我将重构我的设计以使用数组索引而不是整个引用。

也许我不应该使用引用成员 - 我知道有些人说使用指针总是更好。如果我使用指针而不是引用,会有更好的解决方案吗? (我看不到但我不知道。)

【问题讨论】:

  • 有趣的问题!我见过的所有“深拷贝”问题都具有在编译时已知的参考结构并在复制逻辑中硬编码。
  • 你能把 b_array 和 c_array 都替换成一个 map 吗?
  • @GrahamGriffiths 我实际的B 类存储了对c_array 成员的多个引用。我想它可以实现为几个map<int,C>s,但真正的B是一个实现其他代码的类层次结构,所以重构会有点棘手。 (我也倾向于避免使用 STL 代码,我想这主要是出于习惯。)
  • 没有看到您当前如何初始化这些数组,很难给出任何具体建议
  • 复制构造函数需要初始化副本,并且您有一些现有的代码来初始化原始文件。所有重要的细节都在 cmets 中以点点滴滴的形式出现,这使得很难猜测解决方案需要提供什么,或者它可以重用哪些现有(未显示)代码

标签: c++ deep-copy


【解决方案1】:

使用引用的深拷贝必须首先复制值(c_array),然后存储对这些新值的引用。除了您描述的算法之外,我看不到实现此目的的方法。

使用指针代替引用不会改变这一点。有各种comparisons of pointers vs references。指针的初始化不那么棘手(创建为 null 并在您准备好时分配)并且使用起来更危险(可能仍然为 null)。但是您仍然需要复制对象,然后找到并复制它们的链接。

我找不到比使用数组索引更简单/更优雅的方法。使用数组索引,您只需逐个复制数组值,并且将为您处理指向哪个对象的索引结构。

【讨论】:

  • 数组索引的唯一问题是,如果我将c_array 替换为像std::list 这样更易变的东西,那么当它的内容发生变化时,索引都必须更新,这会导致困难-to-track-down 错误,如果它们不同步。 (目前我的代码不这样做,但我很可能希望将来这样做。)当然,我现在看到基于引用成员的实现也有引入滑差错的方法。你的答案可能是正确的,但我会保持开放,以防有人有聪明的解决方案。
  • 我认为指针算术比std::find 好,因为std::find 必须进行线性搜索,而指针算术可以在一次操作中告诉我答案。我已经有了内存地址,所以我不需要搜索元素,只需将它从c_array指针的值中减去即可。
【解决方案2】:

您可以为A 提供一个赋值运算符(和一个复制ctor),它可以同时处理c_arrayb_array 的更改,假设B 可以处理C 的赋值为只是一个引用/指针更新。

它可以是:

struct B { ... B& operator=(const C& c) { this->c = &c; return *this; } };

struct A { ...
    A& operator=(const A& a) {
        c_array = a.c_array;
        b_array = a.b_array;
        // re-assign pointers/references only using B's `operator=(const C&)` :
        std::copy_n(c_array.begin(), c_array.size(), b_array.begin());
        return *this;
    }
};

有点乱,但是:see live example

注意,如果你注释掉std::copy_n这一行,你当然可以在输出中观察到副本没有被分离,即副本的b_array指向原始的c_array而不是自己的。

【讨论】:

  • 谢谢,这是个好主意,但不幸的是,在我的情况下这是不可能的,因为B 包含多个对C 的引用以及其他数据。 (我曾试图将问题简化为最简单的版本,但我现在意识到我应该明确指出 B 包含其他数据 - 我已经更新了问题中的示例代码。对此感到抱歉。)跨度>
  • 据了解B 包含其他数据,但这就是B 的普通赋值运算符和复制ctor 的用途。我的观点是,在你实现了 op 和 copy ctor 之后,使用上面的技术进行一些仅供参考的重写 - 将简单的 std::copy 与自定义的 operator= 结合起来,以省去繁琐的手动重新分配所有引用,我主张简单的B::operator=(const B&)B::operator=(const C&)
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2012-10-04
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2021-01-16
  • 2020-12-16
相关资源
最近更新 更多