Shallow Copy vs Deep Copy in C++
In C++, understanding the distinction between shallow copying and deep copying is critical for managing system memory and avoiding undefined behavior. A shallow copy duplicates an object's member values bit-for-bit, meaning pointers are copied as memory addresses rather than duplicating the data they reference. In contrast, a deep copy allocates dedicated memory for dynamically allocated resources and copies the actual values, ensuring that the original and copied objects remain completely independent. This article breaks down the mechanisms behind shallow and deep copies, highlights their primary differences, and demonstrates how to properly implement them in C++.
What is a Shallow Copy?
A shallow copy creates a new instance of an object and copies all
field values from the original object directly into the new one. If the
object contains only primitive data types (such as int,
float, or char), a shallow copy works
seamlessly because the values themselves are duplicated.
However, if an object contains pointers to dynamically allocated memory on the heap, a shallow copy only duplicates the pointer addresses. Consequently, both the original and the new object point to the exact same block of memory.
class ShallowExample {
public:
int* data;
ShallowExample(int value) {
data = new int(value);
}
// Default compiler-generated copy constructor performs a shallow copy:
// ShallowExample(const ShallowExample& source) : data(source.data) {}
~ShallowExample() {
delete data; // Potential double-free error
}
};Issues with Shallow Copying:
- Dangling Pointers: If one object modifies or frees the shared memory, the other object is left holding an invalid memory address.
- Double-Free Errors: When both objects go out of scope, their destructors attempt to deallocate the same memory address, leading to program crashes.
The C++ compiler automatically provides a default copy constructor and copy assignment operator that perform shallow copies.
What is a Deep Copy?
A deep copy creates an exact replica of an object along with dedicated copies of any dynamically allocated memory the object references. Instead of sharing a pointer to the original memory block, a deep copy allocates a new block of memory on the heap and copies the underlying values into it.
To achieve a deep copy, you must explicitly define a custom copy constructor and copy assignment operator following the C++ Rule of Three (or Rule of Five).
class DeepExample {
public:
int* data;
// Parameterized constructor
DeepExample(int value) {
data = new int(value);
}
// Custom copy constructor for deep copying
DeepExample(const DeepExample& source) {
data = new int(*source.data); // Allocate separate memory and copy the value
}
// Custom copy assignment operator
DeepExample& operator=(const DeepExample& source) {
if (this == &source) {
return *this; // Handle self-assignment
}
delete data; // Clean up existing resource
data = new int(*source.data); // Allocate new memory and copy value
return *this;
}
~DeepExample() {
delete data; // Safely deletes independent memory
}
};Advantages of Deep Copying:
- Independence: Changes made to the dynamically allocated fields of one object do not affect the other.
- Safe Destruction: Each object safely deletes its own allocated memory without risking a double-free scenario.
Key Differences
| Feature | Shallow Copy | Deep Copy |
|---|---|---|
| Memory Allocation | Does not allocate new dynamic memory. | Allocates new dynamic memory on the heap. |
| Pointer Behavior | Copies memory addresses; pointers reference the same location. | Copies the underlying data; pointers reference distinct locations. |
| Implementation | Provided by default by the C++ compiler. | Must be explicitly implemented by the programmer. |
| Resource Safety | Prone to dangling pointers and double-free errors. | Safe from cross-object memory corruption. |
| Performance | Fast, as it only copies fixed-size values. | Slower, requiring system calls to allocate memory and copy buffers. |
When to Use Each
- Use Shallow Copying when your class does not manage
dynamic resources, file handles, or network sockets. If your class
relies entirely on value types or standard library containers (like
std::vectororstd::string), the default shallow copy behavior is often sufficient because standard library containers manage their own deep-copy semantics internally. - Use Deep Copying whenever your class directly
manages raw pointers to dynamic memory. If an object is responsible for
calling
newin its constructor anddeletein its destructor, implementing a deep copy ensures safe and correct object lifecycle management. Modern C++ practices also encourage using smart pointers (such asstd::unique_ptrandstd::shared_ptr) to eliminate the need for manual deep-copy implementations.