Overview
In C++, copying objects can lead to serious bugs if you're dealing with raw pointers. By default, C++ uses shallow copy, which means only the pointer's value is copied—not the data it points to.
This tutorial covers:
- What shallow vs deep copy means
- The problems caused by shallow copy
- How to implement deep copy correctly
- A practical class example with dynamic memory
- When to use
Rule of Three
vs Rule of Five
What Is Shallow Copy?
A shallow copy copies the values of member variables as-is. If your class has a pointer member, both the original and copy point to the same memory.
class Shallow {
public:
int* data;
Shallow(int val) {
data = new int(val);
}
~Shallow() {
delete data;
}
};
Now consider:
Shallow a(10);
Shallow b = a; // default copy constructor
This causes both a.data
and b.data
to point to the same memory. When both destructors run, delete
is called twice on the same pointer — undefined behavior!
What Is Deep Copy?
A deep copy duplicates the actual data pointed to, not just the pointer.
class Deep {
public:
int* data;
Deep(int val) {
data = new int(val);
}
// Copy constructor for deep copy
Deep(const Deep& other) {
data = new int(*other.data);
}
// Assignment operator for deep copy
Deep& operator=(const Deep& other) {
if (this != &other) {
delete data;
data = new int(*other.data);
}
return *this;
}
~Deep() {
delete data;
}
};
Rule of Three
If your class handles dynamic memory:
- Copy Constructor
- Copy Assignment Operator
- Destructor
You must implement all three. This is called the Rule of Three.
Example: Deep Copy for a String Wrapper
class String {
private:
char* buffer;
public:
String(const char* str) {
buffer = new char[strlen(str) + 1];
strcpy(buffer, str);
}
// Copy constructor
String(const String& other) {
buffer = new char[strlen(other.buffer) + 1];
strcpy(buffer, other.buffer);
}
// Assignment operator
String& operator=(const String& other) {
if (this != &other) {
delete[] buffer;
buffer = new char[strlen(other.buffer) + 1];
strcpy(buffer, other.buffer);
}
return *this;
}
~String() {
delete[] buffer;
}
void print() const {
std::cout << buffer << std::endl;
}
};
Usage
String a("Hello");
String b = a; // deep copy
String c("World");
c = a; // deep assignment
All objects manage their own memory independently.
Modern C++: Rule of Five
In C++11 and newer, also consider:
- Move Constructor
- Move Assignment Operator
This is the Rule of Five. Add move semantics if your class is performance-sensitive and uses resource ownership.
Conclusion
When your class uses raw pointers:
- Avoid shallow copies.
- Always implement deep copy logic.
- Follow the Rule of Three (or Rule of Five).
- Prefer
std::string
, std::vector
, or smart pointers in modern C++.
Understanding deep copy is essential for writing robust, bug-free C++ code.