复制构造函数/操作符/函数是否需要明确它实现的副本变体?

Dim*_* C. 13 language-agnostic copy deep-copy shallow-copy

昨天我问了一个关于在C#中复制对象的问题,大多数答案都集中在深拷贝浅拷贝之间的区别,以及应该弄清楚两个拷贝变体中的哪一个给定拷贝构造函数(或运算符或函数)的事实.实现.我发现这很奇怪.

我用C++编写了很多软件,这种语言很大程度上依赖于复制,我从来不需要多种复制变体.我用过的唯一一种复制操作是我称之为" 足够深的复制 ".它执行以下操作:

  • 如果对象拥有成员变量的所有权(参见合成),则会以递归方式复制它.
  • 如果对象对成员变量没有所有权(参见聚合),则仅复制链接.

现在,我的问题有三个:

  • 1)对象是否需要多个副本变体?
  • 2)复制功能是否需要明确它实现的复制变体?
  • 3)顺便说一句,对于我称之为"足够深的复制品",是否有一个更好的术语?我问了一个关于"深层复制"这个术语定义的相关问题.

Mik*_*ron 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;
    }
};
Run Code Online (Sandbox Code Playgroud)

使用此对象,需要处理两个字符串缓冲区.第一个bar是由类本身创建和管理的.克隆对象时,应分配新缓冲区.

baz另一方面,不应该.事实上,你不能,因为你没有足够的信息这样做.应该只复制指针.

当然,foo这只是一个数字.只需复制它,没有别的担心:)

总之,直接回答您的问题:

  1. 99%的时间,没有.复制只有一种方法是有道理的.然而,这种方式有所不同.
  2. 不是直接的.记录它是一个好主意,但内部任何内容都应保持内部.
  3. 只是"深层复制".你应该(编辑:ALMOST)永远不要尝试克隆你不能控制的对象或指针,这样就免除了规则:)


sup*_*cat 5

"深拷贝"与"浅拷贝"之间的区别作为一个实现细节是有意义的,但允许它泄漏超出通常表明有缺陷的抽象,这可能也会以其他方式表现出来.

如果对象Foo仅仅为了封装包含在其中的对象的不可变方面(除了标识之外)而保持对象引用,则正确的副本Foo可以包含引用的副本或对封装对象的副本的引用.

如果一个对象Foo持有一个对象引用,纯粹是出于封装除了identity之外的对象的可变和不可变方面的目的,但是对该对象的引用不会暴露于任何会使其变异的东西,同样的情况也适用.

如果一个对象Foo持有一个对象引用纯粹是为了封装除了identity之外的对象的可变和不可变方面,并且所讨论的对象将要进行变异,那么正确的副本Foo必须包含对封装副本的引用宾语.

如果一个对象Foo持有一个对象引用纯粹是为了封装对象的不可变方面,包括标识,那么正确的副本Foo必须包含引用的副本; 它不能包含对重复对象的引用.

如果对象Foo持有对象引用以封装可变状态和对象标识,则无法单独生成正确的副本Foo.正确的副本Foo只能通过复制它所附着的整个对象集来产生.

谈论"浅拷贝"的唯一时间是将不完整的操作用作制作正确拷贝的步骤之一.否则,只有一个正确的副本"深度",由对象引用中封装的状态类型控制.


小智 2

大多数 C++ 程序员不使用术语“浅复制”和“深复制”,因为通常只有一种方法来复制对象。在 C++ 中尤其如此,因为编译器在许多情况下使用复制构造函数,程序员可以告诉它使用哪个复制构造函数 - 例如:

void f( std::string s );
Run Code Online (Sandbox Code Playgroud)

无法告诉编译器如何复制字符串。