深拷贝与浅拷贝

62

3
深拷贝和浅拷贝有什么区别? - Srinivas Reddy Thatiparthy
1
第一个问题是重复的,但我找不到第二个问题,尽管这可能是因为它没有明确说明。@Ankur:在你的第二个问题中,你是在问默认的复制构造函数吗? - outis
1
在C++中,深拷贝和浅拷贝这些术语通常不会被使用,因为它们与该语言的特性并不是非常相符。在Java和其他几种语言中,由于其基于引用的语义,这种区分更加有用,使得在大多数情况下无法避免浅拷贝。在C++中,由于对象是按值复制的,真正的浅拷贝非常罕见,但默认的复制构造函数也不会实现深拷贝。这些术语在C++中没有意义。 - jalf
3个回答

62

浅拷贝:

拷贝的某些成员可能会引用与原始对象相同的对象:

class X
{
private:
    int i;
    int *pi;
public:
    X()
        : pi(new int)
    { }
    X(const X& copy)   // <-- copy ctor
        : i(copy.i), pi(copy.pi)
    { }
};

在这里,原始对象和复制对象中的pi成员都将指向相同的int


深拷贝:

原始对象的所有成员都被克隆(必要时进行递归)。没有共享对象。

class X
{
private:
    int i;
    int *pi;
public:
    X()
        : pi(new int)
    { }
    X(const X& copy)   // <-- copy ctor
        : i(copy.i), pi(new int(*copy.pi))  // <-- note this line in particular!
    { }
};

在这里,原始对象和复制后的 X 对象的 pi 成员会指向不同的 int 对象,但这两个对象具有相同的值。


默认的复制构造函数(如果您没有自己提供)仅创建浅层副本。

更正:下面的一些评论正确地指出了说默认的复制构造函数总是执行浅层副本(或者深层副本)是错误的。一个类型的复制构造函数是否创建浅层副本、深层副本、或介于两者之间,取决于每个成员的复制行为的组合;毕竟,一个成员类型的复制构造函数可以做任何它想做的事情。

以下是1998年C ++标准第12.8段第8句话对上述代码示例的说明:

X的隐式定义的复制构造函数执行其子对象的逐成员复制。 [...]每个子对象都以其类型适当的方式进行复制:[...]如果子对象是标量类型,则使用内置赋值运算符。


1
这个晦涩的段落似乎不支持你大胆地声称默认的复制构造函数只会进行浅拷贝。 - UncleBens
1
@stakx 你说得对,但我认为这说明“深拷贝”和“浅拷贝”这些术语并不是特别有用的——事实上,我从来没有听过有经验的C++程序员使用它们。 - anon
2
默认的复制构造函数会进行成员逐一复制,而成员的深度或浅度复制取决于成员本身的行为。例如 struct Person { string firstName, lastName; } - 默认的复制构造函数会进行深度复制。 - UncleBens
1
所有可能的复制构造函数“类型”集合并不包括仅为“浅层”和“深层”的不相交集合。它包括中间的所有内容(例如,一半成员可能是浅层复制,另一半可能是深层复制 - 但除非您知道它们的实现,否则您永远不会知道这些成员是否正在进行浅层或深层复制)。默认的复制构造函数既不总是“浅层”也不总是“深层”。浅层和深层只是描述可能的行为。 - franji1
默认对象复制构造函数执行深拷贝,但指针类型的复制构造函数执行浅拷贝。 - Stephen Cross
显示剩余2条评论

16

这种情况的典型例子是指向可变结构体或对象的指针数组。

浅拷贝会复制该数组并保留对原始对象的引用。

深拷贝将复制(克隆)对象,使它们与原始对象毫无关系。 隐含在此中的是对象本身被深度复制了。 这就是困难所在,因为没有真正的方法知道某个东西是否被深度复制。

复制构造函数用于使用先前创建的同一类别的对象来初始化新对象。默认情况下,编译器编写了一个浅拷贝。当未涉及动态内存分配时,浅拷贝可以正常工作,但涉及动态内存分配时,两个对象都将指向堆中的同一内存位置,因此为了解决这个问题,我们写了深拷贝,这样两个对象在内存中都有自己的属性副本。

要阅读具有完整示例和说明的详细信息,请参见构造函数和析构函数文章。

默认的复制构造函数是浅拷贝。您可以根据需要创建深度或浅拷贝的自己的复制构造函数。请参见C++笔记:面向对象编程:复制构造函数


3
应该是指向可变对象的指针数组。 - David Rodríguez - dribeas
一个很好的例子是Boost::GIL,其中视图是图像对象(像素容器)的浅拷贝。当通过视图对象访问像素时,会创建局部/临时的像素副本。 - jiggunjer

4
深拷贝字面上执行深拷贝。这意味着,如果你的类有一些引用字段,它们的值将被复制,而不是引用本身。例如,如果你有两个类A和B的实例,具有引用类型的字段,并执行深拷贝,则在A中更改该字段的值不会影响B中的值。反之亦然。浅拷贝的情况则不同,因为只有引用被复制,因此,在复制的对象中更改此字段将影响原始对象。

复制构造函数执行哪种类型的复制?

这取决于实现。这意味着没有严格的规定,你可以像深拷贝或浅拷贝一样实现它,但据我所知,实现深拷贝是常见做法。默认的复制构造函数执行浅拷贝。

这是否意味着我可以通过 int ivar; 实现浅拷贝? - kesarling He-Him

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接