Default assignment operator= in c++ is a shallow copy?

54.7k views Asked by At

Just a simple quick question which I couldn't find a solid answer to anywhere else. Is the default operator= just a shallow copy of all the class' members on the right hand side?

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

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

would be identical to:

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

This seems to be true when I test it but I need to be sure I'm not missing some specific case.

8

There are 8 answers

0
Mark B On

Yes, it just copies the object member-wise, which can cause issues for raw pointers.

4
Sarfaraz Nawaz On

Yes, default operator= is a shallow copy.

By the way, the actual difference between shallow copy and deep copy becomes visible when the class has pointers as member fields. In the absence of pointers, there is no difference (to the best of my knowledge)!

To know the difference between them, see these topics (on stackoverflow itself):

0
TimA On

If a, b and c were classes then the assignment operator for those classes would be called, so the compiler isn't simply copying the raw memory contents - but as others pointed out, any raw pointers will be copied without any attempt to duplicate the pointed-to thing, thus giving you the potential for dangling pointers.

2
Steve Jessop On

I'd say, default operator= is a copy. It copies each member.

The distinction between a shallow copy and a deep copy doesn't arise unless the members being copied are some kind of indirection such as a pointer. As far as the default operator= is concerned, it's up to the member being copied what "copy" means, it could be deep or shallow.

Specifically, though, copying a raw pointer just copies the pointer value, it doesn't do anything with the referand. So objects containing pointer members are shallow-copied by default operator=.

There are various efforts at writing smart pointers that perform clone operations on copying, so if you use those everywhere in place of raw pointers then the default operator= will perform a deep copy.

If your object has any standard containers as members, then it may be confusing to (for example) a Java programmer to say that operator= is a "shallow copy". In Java a Vector member is really just a reference, so "shallow copy" means that Vector members aren't cloned: source and destination refer to the same underlying vector object. In C++ a vector member will be copied, along with its contents, since the member is an actual object not a reference (and vector::operator= guarantees the contents are copied with it).

If your data member is a vector of pointers, then you don't have either a deep copy or a shallow copy. You have a semi-deep copy, where the source and destination objects have separate vectors, but the corresponding vector elements from each still point to the same, uncloned object.

1
Edward Strange On

No. operator= doesn't perform a copy at all. It's an assignment operator, not copy operator.

The default assignment operator assigns each member.

0
Jack Saalwächter On

"shallow" versus "deep" copy is less meaningful in C++ than it is in C or Java.

To illustrate this, I've changed your Foo class from three ints to an int, an int*, and a 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;
}

When this program is run, it yields the following output:

f1.b: 0x100100080 &f1.c[0]: 0x100100090
f2.b: 0x100100080 &f2.c[0]: 0x1001000a0

The int is boring, so I've left it out. But look at the difference between the int* and the vector<int>: the int* is the same in f1 and f2; it's what you would call a "shallow copy". The vector<int> however is different between f1 and f2; it's what you would call a "deep copy".

What's actually happened here is that the default operator = in C++ behaves as if the operator = for all of its members were called in order. The operator = for ints, int*s, and other primitive types is just a byte-wise shallow copy. The operator = for vector<T> performs a deep copy.

So I would say the answer to the question is, No, the default assignment operator in C++ does not perform a shallow copy. But it also doesn't perform a deep copy. The default assignment operator in C++ recursively applies the assignment operators of the class's members.

0
kallumama24 On

As illustrated by the code snippet below, the = (assignment) operator for STL performs a deep copy.

#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;
}
0
fabian On

I personally like the explanation in Accelerated c++. The text (page 201) says:

If the class author does not specify the assignment operator, the compiler synthesizes a default version. The default version is defined to operate recursively - assigning each data element according to the appropriate rules for the type of that element. Each member of a class type is assigned by calling that member's assignment operator. Members that are of built-in type are assigned by assigning their values.

As the members in the question are integers, I understand that they are deep copied. The following adaptation of your example illustrates this:

#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;
}

which prints:

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