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::vector or std::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 new in its constructor and delete in its destructor, implementing a deep copy ensures safe and correct object lifecycle management. Modern C++ practices also encourage using smart pointers (such as std::unique_ptr and std::shared_ptr) to eliminate the need for manual deep-copy implementations.