Copy Constructors and Assignment Operators

This post will introduce two members in C++ class. The first is copy constructor that can perform a deep copy for member variables. Another one is assignment operator which is used to assign one to another by overloading = operator.

Compiler provided

Usually, if there is no copy constructors and assignment operator in a class, the compiler will give default ones of member-wise copy version.

copy_constructor_and_assignment_operator
1
2
Myclass (const Myclass& a)
Myclass& operator = (const Myclass& a);

which means, for a class:

1
2
3
4
5
class MyClass {
int x;
char c;
std::string s;
};

the default copy constructor and assignment operator provided by compiler like:
1
2
3
4
5
6
7
8
9
10
11
12
// default copy constructor:
MyClass::MyClass( const MyClass& other ) :
x( other.x ), c( other.c ), s( other.s )
{}
// default assignment operator:
MyClass& MyClass::operator=( const MyClass& other ) {
x = other.x;
c = other.c;
s = other.s;
return *this;
}

In many cases, this is sufficient. However, there are some circumstances where the member-wise copy version is not good enough. The commont reason of that is the object contains a raw pointer witch points to a block of memory. In this case, the member-wise copy is likely to lead to more than one pointers that points to the same block of memory which will be a heap curruption in objects destruction.

To avoid this curruption, copy constructor and assignment operator need to be implemented.

Copy constructor & Assignment operator

copy_constructor
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
#include <iostream>
#include <cstring>
using namespace std;
class String {
public:
String(const char* str);
String(const String& other);
String& operator=(const String& other);
~String();
private:
char *m_data;
};
String::String(const char* str) {
cout << "constructor" << endl;
if (str == NULL) {
m_data = new char[1];
*m_data = '\0';
} else {
if (m_data)
delete[] m_data;
int length = strlen(str);
m_data = new char[length + 1];
strcpy(m_data, str);
}
}
String::String(const String& other) {
cout << "copy constructor" << endl;
int length = strlen(other.m_data);
m_data = new char[length + 1];
strcpy(m_data, other.m_data);
}
String& String::operator=(const String& other) {
cout << "assignment operator" << endl;
if (this == &other) {
return *this;
} else {
delete[] m_data;
int length = strlen(other.m_data);
m_data = new char[length + 1];
strcpy(m_data, other.m_data);
return *this;
}
}
String::~String() {
cout << "destructor" << endl;
delete [] m_data;
}
int main() {
cout << "a(\"abc\")" << endl;
String a("abc"); // constructor.
cout << "b(\"cde\")" << endl;
String b("cde"); // constructor.
cout << " d = a" << endl;
String d = a; // copy constructor. (NOTE)
cout << "c(b)" << endl;
String c(b); // copy constructor.
cout << "c = a" << endl;
c = a; // assignment operator.
cout << endl;
}

Many of the STL containers and algorithms require that an object should be copyable. Typically, this means that you need to have the copy constructor that takes a const reference.

Disallow Copy constructor and Assignment operator

In some cases, it is not a safe operation to copy or assign an object. So, its copy constructor or assignment operator should be disabled explicitly. The most commont way for this is to make private constructor or operator and provide no implementation. Generally, a macro can be used to simplify this procedure.

Disallow_copy_and_assign
1
2
3
4
5
6
7
8
9
10
11
12
13
// define a macro.
#define DISALLOW_COPY_AND_ASSIGN(TypeName) \
TypeName(const TypeName&);
void operator=(const TypeName&)
// use the macro.
class Foo {
public:
explicit Foo(int f);
private:
DISALLOW_COPY_AND_ASSIGN(Foo);
}

push_back() of STL vector container

push_back() operator in vector container will perform a deep-copy of object. Actually, push_back() will call insert() in low level.

push_back_in_vector_container
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
#include <vector>
#include <iostream>
using namespace std;
typedef struct point {
int x;
int y;
}Point;
ostream& operator<<(ostream& output, const Point &a) {
return output << a.x <<" "<< a.y;
}
int main() {
Point * a = new Point;
vector<Point> PointList;
a->x = 3;
a->y = 4;
PointList.push_back(*a);
a->x = 4;
a->y = 4;
PointList.push_back(*a);
a->x = 5;
a->y = 4;
PointList.push_back(*a);
delete a;
for (vector<Point>::iterator i = PointList.begin(); i != PointList.end(); i++) {
cout << (*i)<< endl;
}
return 0;
}

result is:

result
1
2
3
3 4
4 4
5 4