默认赋值运算符=在c ++中是浅拷贝?

时间:2011-02-23 20:09:15

标签: c++ operators shallow-copy

这是一个简单的快速问题,我找不到其他任何地方的可靠答案。默认运算符=只是右侧所有类成员的浅表副本吗?

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

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

与:

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

当我测试它时,这似乎是真的,但我需要确定我没有错过某些特定情况。

8 个答案:

答案 0 :(得分:37)

我会说,默认operator=副本。它复制每个成员。

除非被复制的成员是某种间接性(如指针),否则不会出现浅拷贝和深拷贝之间的区别。就默认的operator=而言,由复制的成员决定“复制”的含义,它可能是深的还是浅的。

具体来说,复制原始指针只是复制指针值,但它不会对referand执行任何操作。所以包含指针成员的对象默认为浅层复制operator=

编写在复制时执行克隆操作的智能指针有各种各样的工作,所以如果你用它们来代替原始指针,那么默认的operator=将执行深层复制。

如果您的对象有任何标准容器作为成员,那么(例如)Java程序员说operator=是“浅拷贝”可能会让人感到困惑。在Java中,Vector成员实际上只是一个引用,因此“浅拷贝”意味着不会克隆Vector成员:源和目标引用相同的底层矢量对象。在C ++中,vector成员与其内容一起被复制,因为该成员是实际对象而不是引用(vector::operator=保证内容随之复制)

如果您的数据成员是指针的向量,那么您没有深层副本浅层副本。您有一个半深拷贝,其中源和目标对象具有单独的向量,但每个对应的向量元素仍然指向相同的未克隆对象。

答案 1 :(得分:14)

是的,默认operator=是一个浅层副本。

顺便说一下,当类有指针作为成员字段时,shallow copydeep copy之间的实际差异变得可见。在没有指针的情况下,没有区别(据我所知)!

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

答案 2 :(得分:9)

是的,它只是成员复制对象,这可能会导致原始指针出现问题。

答案 3 :(得分:7)

“浅”与“深”副本在C ++中的意义不如在C或Java中有意义。

为了说明这一点,我已将您的Foo课程从三个int更改为intint*vector<int>:< / p>

#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>之间的差异:int*在f1和f2中是相同的;这就是你所说的“浅层副本”。然而vector<int>在f1和f2之间是不同的;这就是你所说的“深层复制品”。

这里实际发生的事情是C ++中的默认operator =行为就好像所有成员的operator =按顺序调用一样。 operator =int和其他基本类型的int*只是一个逐字节浅的副本。 operator = vector<T>执行深层复制。

所以我想说问题的答案是,不,C ++中的默认赋值运算符不执行浅拷贝。但它也没有执行深层复制。 C ++中的默认赋值运算符以递归方式应用类成员的赋值运算符。

答案 4 :(得分:2)

我个人喜欢Accelerated 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

答案 5 :(得分:1)

如果a,b和c是类,那么将调用这些类的赋值运算符,因此编译器不仅仅是复制原始内存内容 - 但正如其他人所指出的那样,任何原始指针都将被复制而不会有任何尝试复制指向的东西,从而为你提供悬空指针的潜力。

答案 6 :(得分:1)

没有。 operator=根本不执行副本。它是赋值运算符,而不是 copy 运算符。

默认赋值运算符指定每个成员。

答案 7 :(得分: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;
}