Copy and dynamic memory allocation

Default copy constructor and copy operator

Usually, when we use dynamic memory allocation inside a class, we allocate the memory inside its constructor and free it inside its destructor.

Look at the following example:

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
class A { public: A(unsigned arraySize) : _arraySize(arraySize) { // Dynamically allocates an array. _array = new int[_arraySize]; } ~A() { // Frees the previously allocated array. delete[] _array; } private: int *_array; unsigned _arraySize; }; int main() { A a(100); A b(100); b = a; return 0; }

It compiles without error, but it crashes in its execution. To understand why, we must first think about what the default copy operator and copy constructor do. The default copy constructor creates the object by copying the value of the member variables of the object it receives in argument. The default copy operator copies the value of the member variables of the object in right operand into the object in left operand.

Now, if we look at the example above, what happens is that both the objects a and b, in their constructor, allocate an array of 100 elements of type int, and save the address of the array into their member variable _array. When the line 'b = a;' is reached, the member b._array is assigned with the value of a._array, so now the member _array of both the objects a and b points to the array the object a allocated, and the address of the array the object b allocated is lost.

When the end of the function main is reached, the destructor of the object b is called. It deletes the array its member _array points to, which is the array the object a allocated. After that, the destructor of the object a is called and it tries to delete the array its member _array points to, but it has already been deleted by the destructor of the object b, so the program crashes.

The result is that the array allocated by the object b is not deleted and the program crashes, because an attempt to delete an already deleted memory space is executed.

Defining the copy constructor and the copy operator

The solution to the problem above is to define the copy constructor and copy operator of the class. What they do, do not mater so much. What is crucial is that they do not assign the member pointers (That point to dynamically allocated memory spaces) of the object to create, with the value of the member pointers of the object to copy. Also, if the copy operator alters the value of the member pointers (That point to dynamically allocated memory spaces), it should, before, delete the dynamically allocated memory spaces they point to (If they do).

Here is an example, where the copy constructor and copy operator are defined so they allocate a new memory space and copy the data, from the array of the object to copy, into it:

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
73
74
75
76
77
78
79
#include <iostream> class A { public: A(unsigned arraySize) : _arraySize(arraySize) { std::cout << "A(unsigned arraySize)\n"; // Dynamically allocates an array. _array = new int[_arraySize]; } // Declares the copy operator here, so the copy constructor can use it. A & operator=(const A &obj); // Copy constructor. A(const A &obj) { /* Assigns the pointer with the address 0, so the copy operator does not try to delete the memory space it points to. */ _array = 0; operator=(obj); // Or: // (*this) = obj; } ~A() { std::cout << "~A()\n"; // Frees the previously allocated array. delete[] _array; } private: int *_array; unsigned _arraySize; }; // Defines the copy operator. A & A::operator=(const A &obj) { std::cout << "A & operator=(const A &obj)\n"; delete[] _array; // Deletes the array it points to, if it does not point to the address 0. if(obj._arraySize > 0) // If the array to copy is not empty. { _arraySize = obj._arraySize; // Allocates a new memory space. _array = new int[_arraySize]; // Copies the values from 'obj._array' into '_array'. for(unsigned c = 0; c < _arraySize; c++) _array[c] = obj._array[c]; } else { _array = 0; _arraySize = 0; } return *this; } int main() { A a(100); A b(100); b = a; A c(b); return 0; }

Now, the problem is solved, because all allocated memory spaces are deleted and no attempt to delete an already deleted array is executed.

Note that we must do the same thing for member pointers pointing to memory spaces allocated with new (Instead of new[]) and other functions like malloc and calloc.