Introduction
When you copy an object (Buffer b = a;, passing it by value, returning it), C++ copies it member by member. That is fine for numbers and strings. It is a trap for a pointer member: the copy gets the same address, so both objects share one block of memory.
Copies happen all the time in C++: when you initialise one object from another, pass by value, or return a value. For most classes the automatic copy is right. For a class that owns heap memory, it is a bug waiting to happen.
How it works
The shallow-copy bug
#include <iostream>
using namespace std;
class Buffer {
public:
int* data;
int size;
Buffer(int n) : size(n) {
data = new int[n];
for (int i = 0; i < n; i++) data[i] = 0;
}
~Buffer() {
delete[] data;
}
};
int main() {
Buffer a(3);
Buffer b = a; // copies the pointer, not the numbers
b.data[0] = 99;
cout << a.data[0] << endl;
return 0;
}Buffer b = a; copies data, the pointer: watch both arrows point to the same heap block. Writing b.data[0] = 99 changes a's numbers too. Worse, at the end of main both destructors run delete[] on the same block. StepVioz stops at the second delete[]; compiled C++ may crash, or quietly corrupt memory.
Syntax
class Buffer {
public:
Buffer(const Buffer& other); // copy constructor
Buffer& operator=(const Buffer& other); // copy assignment
~Buffer(); // destructor
};
Example
#include <iostream>
using namespace std;
class Buffer {
public:
int* data;
int size;
Buffer(int n) : size(n) {
data = new int[n];
for (int i = 0; i < n; i++) data[i] = 0;
}
Buffer(const Buffer& other) : size(other.size) {
data = new int[size];
for (int i = 0; i < size; i++) data[i] = other.data[i];
}
Buffer& operator=(const Buffer& other) {
if (this != &other) {
delete[] data;
size = other.size;
data = new int[size];
for (int i = 0; i < size; i++) data[i] = other.data[i];
}
return *this;
}
~Buffer() {
delete[] data;
}
};
int main() {
Buffer a(3);
Buffer b = a;
b.data[0] = 99;
cout << a.data[0] << " " << b.data[0] << endl;
Buffer c(1);
c = a;
cout << c.size << endl;
return 0;
}The fix is to say what copying means:
- The copy constructor,
Buffer(const Buffer& other), runs forBuffer b = a;. It allocates a new block and copies the numbers into it: a deep copy. - The copy assignment operator,
operator=, runs forc = a;between two objects that already exist. It must release its old block first, and must survivea = a;(thethis != &othertest). - The destructor frees the block.
The rule of three: a class that needs one of these three almost always needs all three. Now each object owns its own block, the arrows point to different places, and every block is deleted exactly once.
return *this; returns the object itself (by reference), so assignments can be chained: a = b = c;.
Better still: use members that copy themselves correctly, such as vector<int> instead of int *. Then the compiler's member-by-member copy is already deep, and you need none of the three. This is called the rule of zero.
Line by line
- The
Buffer(int n)constructor allocatesnints withnewand sets them to 0. - The copy constructor allocates a new block of the same size and copies every number into it.
operator=checksthis != &other, frees its own block, allocates a new one and copies the numbers. It returns*this.- The destructor frees the block.
Buffer b = a;runs the copy constructor, so changingb.data[0]to 99 leavesa.data[0]at 0.c = a;runs the copy assignment, soc.sizebecomes 3. At the end, each of the three blocks is deleted exactly once.
Expected result
0 99
3
In the real world
The standard library's containers, such as vector and string, are deep-copying classes written with exactly these member functions. That is why copying a vector gives a truly separate list.
Common mistakes
- Writing a copy constructor but forgetting the copy assignment operator, so
c = a;is still shallow. - Freeing the old block in
operator=before checking for self-assignment. - Returning
voidfromoperator=, which stops chains such asa = b = c;.
Best practices
- Prefer members that copy themselves, and follow the rule of zero.
- If a class owns a raw resource, write all three or delete copying with
= delete. - Test copying, assignment and self-assignment for every class that owns memory.
Summary
Copying a class copies its members one by one, which shares pointer members and leads to double deletes. A copy constructor, a copy assignment operator and a destructor together make deep copies, and members such as vector avoid the problem entirely.
Quick check
Key takeaways
- The default copy copies pointer values, not what they point to.
- A deep copy gives each object its own block.
- operator= must release the old block and survive a = a.
- Members that copy themselves, like vector, need none of the three.
Practice problems for this lesson
My notes
Write down what you want to remember from this lesson. Notes are saved on this device as you type, and appear on your dashboard.