C++中的默认赋值运算符=是浅拷贝吗?

53

这只是一个简单的快速问题,我在其他地方找不到确切的答案。默认的 operator= 是将右侧所有类成员进行浅拷贝吗?

Class foo {
public:
  int a, b, c;
};

foo f1, f2;
...
f1 = f2;

将会与以下内容完全相同:

f1.a = f2.a;
f1.b = f2.b;
f1.c = f2.c;

我测试时似乎是对的,但我需要确保我没有遗漏某些特定情况。


请查看以下链接:https://dev59.com/oHI-5IYBdhLWcg3whYwr#1810320。 - Martin York
8个回答

59

我觉得,默认的operator=是一种复制,它会复制每个成员。

只有在被复制的成员是某种间接引用(如指针)时,才会出现浅复制和深复制之间的区别。就默认的operator=而言,要复制什么内容取决于被复制的成员,它可以是深度复制或者浅复制。

具体来说,复制一个裸指针只会复制指针值,不会对所指对象进行任何操作。因此,包含指针成员的对象默认使用浅复制的operator=

有些人尝试编写智能指针以执行克隆操作,这样如果你在所有地方使用它们代替裸指针,那么默认的operator=将执行深拷贝。

如果你的对象有任何标准容器作为成员,那么对于像Java程序员这样的人来说,说operator=是"浅拷贝"可能会令人困惑。在Java中,Vector成员实际上只是一个引用,所以"浅拷贝"意味着Vector成员没有被克隆:源和目标引用同一个底层向量对象。在C++中,vector成员将会被复制,连同其内容一起,因为该成员是一个实际的对象而不是一个引用,并且vector::operator=保证了与之相应的内容也被复制。

如果你的数据成员是指针向量,则既不是深拷贝也不是浅拷贝。你有一个半深度拷贝,源和目标对象具有单独的向量,但每个相应的向量元素仍然指向同一个未克隆的对象。


复制元素时将使用它们的复制构造函数还是复制赋值运算符? - Gauthier
@Gauthier:赋值。 - Steve Jessop

23

是的, 默认的operator= 是浅拷贝。

顺便说一下,当类有指针作为成员字段时,浅拷贝深拷贝 的实际区别会变得可见。在没有指针的情况下,据我所知,它们之间没有区别!

要了解它们之间的区别,请参阅以下主题(在stackoverflow本身):


1
@Steve 是正确的,编译器将生成对每个成员的赋值运算符的调用,这通常会导致深拷贝。 - Ben Voigt
1
@Ben:我知道赋值运算符会为每个成员调用。问题是:如果类有指针呢?这不是深拷贝。因此默认的 operator= 是浅拷贝。 - Nawaz
1
如果它们是裸指针,那么是浅拷贝。如果它们是智能指针,那么取决于智能指针赋值运算符的实现方式。 - Ben Voigt
@Ben:是的。这也是我在我的帖子中所说的。 - Nawaz

12

在C++中,“浅拷贝”和“深拷贝”比在C或Java中意义更小。

为了说明这一点,我已将您的Foo类从三个int更改为一个int,一个int*和一个vector<int>

#include <iostream>
#include <vector>

class Foo {
public:
  int a;
  int *b;
  std::vector<int> c;
};

using namespace std;

int main() {
  Foo f1, f2;
  f1.a = 42;
  f1.b = new int(42);
  f1.c.push_back(42);
  f2 = f1;

  cout << "f1.b: " << f1.b << " &f1.c[0]: " << &f1.c[0] << endl;
  cout << "f2.b: " << f2.b << " &f2.c[0]: " << &f2.c[0] << endl;
}
当运行此程序时,它会产生以下输出:
f1.b: 0x100100080 &f1.c[0]: 0x100100090
f2.b: 0x100100080 &f2.c[0]: 0x1001000a0

int类型有点无聊,所以我将其省略了。但请看一下int*vector<int>之间的差异:在f1和f2中,int*是相同的;这被称为“浅拷贝”。然而,vector<int>在f1和f2之间是不同的;这被称为“深拷贝”。

实际发生的事情是,C++中的默认operator=的行为就好像按顺序调用了所有成员的operator=一样。对于intint*和其他基本类型,operator=只是一个以字节为单位的浅拷贝。对于vector<T>operator=执行深度拷贝。

因此,我会说答案是,在C ++中,默认赋值运算符并不执行浅拷贝。但它也不执行深拷贝。在C++中,默认赋值运算符递归应用类的成员的赋值运算符。


10

是的,它只是逐成员复制对象,这可能会对裸指针造成问题。


5

我个人喜欢在《C++ 快速入门》中的解释。文本(第201页)说:

如果类作者没有指定赋值运算符,编译器将合成默认版本。默认版本被定义为递归操作,根据该元素类型的适当规则分配每个数据元素。每个类类型的成员都通过调用该成员的赋值运算符进行分配。内置类型的成员通过分配它们的值来进行分配。

由于问题中的成员是整数,所以我理解它们被深度复制了。下面对您示例的改编说明了这一点:

#include<iostream>
class foo {
public:
  int a, b, c;
};

int main() {
    foo f1, f2;
    f1 = f2;
    std::cout << "f1.a and f2.a are: " << f1.a << " and " << f2.a << std::endl;
    f2.a = 0;
    std::cout << "now, f1.a and f2.a are: " << f1.a << " and " << f2.a << std::endl;
}

将打印:

f1.a and f2.a are: 21861 and 21861
now, f1.a and f2.a are: 21861 and 0

2

不,operator=根本不执行复制。它是一个赋值运算符,而不是拷贝运算符。

默认的赋值运算符会为每个成员赋值。


赋值运算符如果被定义,会进行深拷贝;如果没有被定义,则会进行指针的浅拷贝。 - user1043000

1
如果a、b和c是类,那么这些类的赋值运算符将被调用,因此编译器不仅仅是复制原始内存内容 - 但正如其他人指出的那样,任何原始指针都将被复制而没有尝试复制指向的东西,从而给您带来悬空指针的潜在风险。

0
如下面的代码片段所示,STL中的=(赋值)运算符执行深复制。
#include <iostream>
#include <stack>
#include <map>
#include <vector>

using namespace std;

int main(int argc, const char * argv[]) {
    /* performs deep copy */
    map <int, stack<int> > m;
    stack <int> s1;
    stack <int> s2;

    s1.push(10);
    cout<<&s1<<" "<<&(s1.top())<<" "<<s1.top()<<endl;   //0x7fff5fbfe478 0x100801200 10

    m.insert(make_pair(0, s1));
    cout<<&m[0]<<" "<<&(m[0].top())<<" "<<m[0].top()<<endl; //0x100104248 0x100803200 10

    m[0].top() = 1;
    cout<<&m[0]<<" "<<&(m[0].top())<<" "<<m[0].top()<<endl; //0x100104248 0x100803200 1

    s2 = m[0];
    cout<<&s2<<" "<<&(s2.top())<<" "<<s2.top()<<endl;   //0x7fff5fbfe448 0x100804200 1

    s2.top() = 5;
    cout<<&s2<<" "<<&(s2.top())<<" "<<s2.top()<<endl;   //0x7fff5fbfe448 0x100804200 5
    cout<<&m[0]<<" "<<&(m[0].top())<<" "<<m[0].top()<<endl; //0x100104248 0x100803200 1

    cout<<endl<<endl;

    map <int, stack<int*> > mp;
    stack <int*> s1p;
    stack <int*> s2p;

    s1p.push(new int);
    cout<<&s1p<<" "<<&(s1p.top())<<" "<<s1p.top()<<endl;    //0x7fff5fbfe360 0x100805200 0x100104290

    mp.insert(make_pair(0, s1p));
    cout<<&mp[0]<<" "<<&(mp[0].top())<<" "<<mp[0].top()<<endl;  //0x1001042e8 0x100806200 0x100104290

    mp[0].top() = new int;
    cout<<&mp[0]<<" "<<&(mp[0].top())<<" "<<mp[0].top()<<endl;  //0x1001042e8 0x100806200 0x100104320

    s2p = mp[0];
    cout<<&s2p<<" "<<&(s2p.top())<<" "<<s2p.top()<<endl;    //0x7fff5fbfe330 0x100807200 0x100104320

    s2p.top() = new int;
    cout<<&s2p<<" "<<&(s2p.top())<<" "<<s2p.top()<<endl;    //0x7fff5fbfe330 0x100807200 0x100104340
    cout<<&mp[0]<<" "<<&(mp[0].top())<<" "<<mp[0].top()<<endl;  //0x1001042e8 0x100806200 0x100104320

    cout<<endl<<endl;

    vector<int> v1,v2;
    vector<int*> v1p, v2p;

    v1.push_back(1);
    cout<<&v1<<" "<<&v1[0]<<" "<<v1[0]<<endl;   //0x7fff5fbfe290 0x100104350 1

    v2 = v1;
    cout<<&v2<<" "<<&v2[0]<<" "<<v2[0]<<endl;   //0x7fff5fbfe278 0x100104360 1

    v2[0] = 10;
    cout<<&v2<<" "<<&v2[0]<<" "<<v2[0]<<endl;   //0x7fff5fbfe278 0x100104360 10
    cout<<&v1<<" "<<&v1[0]<<" "<<v1[0]<<endl;   //0x7fff5fbfe290 0x100104350 1

    cout<<endl<<endl;

    v1p.push_back(new int);
    cout<<&v1p<<" "<<&v1p[0]<<" "<<v1p[0]<<endl;    //0x7fff5fbfe260 0x100104380 0x100104370

    v2p = v1p;
    cout<<&v2p<<" "<<&v2p[0]<<" "<<v2p[0]<<endl;    //0x7fff5fbfe248 0x100104390 0x100104370

    v2p[0] = new int;
    cout<<&v2p<<" "<<&v2p[0]<<" "<<v2p[0]<<endl;    //0x7fff5fbfe248 0x100104390 0x1001043a0
    cout<<&v1p<<" "<<&v1p[0]<<" "<<v1p[0]<<endl;    //0x7fff5fbfe260 0x100104380 0x100104370

    return 0;
}

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