昨天我在C#中问过a question about copying objects,大多数答案都集中在深拷贝和浅拷贝之间的区别,以及它应该被制作的事实清除给定复制构造函数(或运算符或函数)实现的两个复制变体中的哪一个。我发现这很奇怪。
我用C ++编写了很多软件,这种语言严重依赖于复制,我从来不需要多种复制变体。我用过的唯一一种复制操作是我称之为“足够深的复制”。它执行以下操作:
现在,我的问题有三个:
答案 0 :(得分:5)
对象只需复制需要复制的内容。虽然这个问题标记为语言不可知,并且你提到了C ++,但我更喜欢用C#术语解释(因为,这是我最熟悉的)。但是,概念是相似的。
值类型就像结构一样。它们直接存在于对象实例中。因此,复制对象时,除了复制值类型外别无选择。所以,你通常不必担心这些。
引用类型就像指针一样,这就是它变得棘手的地方。根据引用类型的不同,您可能需要也可能不需要深层复制。一般的经验法则是,如果引用类型(作为对象的成员)依赖于外部对象的状态,则应该克隆它。如果不是,而且永远不会,那就不一定了。
另一种思维方式是从外部传入对象的对象可能不应该被克隆。你的类生成的对象应该是。
好吧,我说谎了,我会使用一些C ++,因为它最能解释我的意思。
class MyClass {
int foo;
char * bar;
char * baz;
public: MyClass(int f, char * str) {
this->foo = f;
bar = new char[f];
this->baz = str;
}
};
使用此对象,需要处理两个字符串缓冲区。第一个bar
由类本身创建和管理。克隆对象时,应分配一个新缓冲区。
baz
不应该。事实上,你不能,因为你没有足够的信息这样做。指针应该被复制。
当然,foo
只是一个数字。只需复制它,没有别的担心:)
总之,直接回答您的问题:
答案 1 :(得分:5)
“深拷贝”与“浅拷贝”之间的区别作为一个实现细节是有意义的,但允许它泄漏超出通常表明有缺陷的抽象,这可能会以其他方式表现出来。
如果对象Foo
持有对象引用纯粹是为了封装其中包含的对象的不可变方面而不是标识,那么Foo
的正确副本可以包含引用的副本或对封装对象的副本的引用。
如果对象Foo
持有对象引用纯粹是为了封装除了identity 之外的对象的可变和不可变方面,但是对该对象的引用将永远不会暴露给任何会改变它的东西,同样的情况也适用。
如果一个对象Foo
拥有一个对象引用,纯粹是为了封装一个对象而不是identity 的可变和不可变方面,并且所讨论的对象将会被变异,然后Foo
的正确副本必须包含对封装对象副本的引用。
如果对象Foo
持有对象引用纯粹是为了封装对象的不可变方面,包括标识,那么Foo
的正确副本必须包含重复项参考;它必须 NOT 包含对重复对象的引用。
如果对象Foo
拥有对象引用以封装可变状态和对象标识,则无法单独生成Foo
的正确副本。 Foo
的正确副本只能通过复制与其相连的整个对象集来生成。
讨论“浅拷贝”的唯一时间是将不完整的操作用作制作正确拷贝的步骤之一。否则,只有一个正确的副本“深度”,由对象引用中封装的状态类型控制。
答案 2 :(得分:2)
大多数C ++程序员不使用术语“浅拷贝”和“深拷贝”,这是因为通常只有一种方法可以复制对象。在C ++中尤其如此,因为编译器在许多情况下使用复制构造函数,程序员可以告诉它使用哪个复制构造函数 - 例如:
void f( std::string s );
没有办法告诉编译器如何复制字符串。
答案 3 :(得分:0)
有点迟到的答案,但是c ++ 11或多或少地涵盖了你:
正如this answer to Which kind of pointer do I use when?中详述的那样,解决方案是使用不同的pointertype来表达您拥有的(共享)所有权类型。
由于std::unique_ptr
不可复制,您将被迫复制唯一指针所拥有的数据。根据成员所有权陈述所有内容可能总是清楚说明在哪个成员上使用哪种副本。