Fundamentals 16 min read

Master Deep vs Shallow Copy in C++ and Follow the Rule of Three/Five

This article explains the concepts of shallow and deep copying in C++, demonstrates their pitfalls such as shared memory and double deletion, and shows how to implement proper copy constructors, assignment operators, and move semantics following the Rule of Three/Five to ensure safe and efficient resource management.

Liangxu Linux
Liangxu Linux
Liangxu Linux
Master Deep vs Shallow Copy in C++ and Follow the Rule of Three/Five

Copying in C++: Shallow vs Deep

Copying an object creates a new instance from an existing one. A shallow copy copies only the pointer or reference, so the original and the copy share the same memory. A deep copy allocates new memory and copies the actual data, giving each object its own independent storage.

Simple Example of Shallow Copy

#include <iostream>
using namespace std;

class MyClass {
public:
    char* data;
    MyClass(const char* str) {
        data = new char[strlen(str) + 1];
        strcpy(data, str);
    }
    // Shallow copy constructor (default behavior)
    MyClass(const MyClass& other) {
        data = other.data; // copies only the pointer
    }
    void print() const { cout << "Data: " << data << endl; }
    ~MyClass() { delete[] data; }
};

int main() {
    MyClass obj1("Hello");
    MyClass obj2 = obj1; // shallow copy – shared memory
    obj1.print();
    obj2.print();
    return 0;
}

Both obj1 and obj2 point to the same buffer. Modifying one affects the other, and when both objects are destroyed they each call delete[] on the same pointer, causing a double‑free error.

Problems Caused by Shallow Copy

Shared state : Changing data through one object changes it for the other.

Double free : Both destructors attempt to release the same memory, leading to crashes or undefined behaviour.

Memory leak : If a shallow assignment overwrites a pointer without freeing the old buffer, the original memory is lost.

Implementing a Deep Copy Constructor

class MyClass {
public:
    char* data;
    MyClass(const char* str) {
        data = new char[strlen(str) + 1];
        strcpy(data, str);
    }
    // Deep copy constructor
    MyClass(const MyClass& other) {
        data = new char[strlen(other.data) + 1];
        strcpy(data, other.data);
    }
    void print() const { cout << "Data: " << data << endl; }
    ~MyClass() { delete[] data; }
};

int main() {
    MyClass obj1("Hello");
    MyClass obj2 = obj1; // deep copy
    obj1.data[0] = 'h'; // modify obj1 only
    obj1.print(); // prints "hello"
    obj2.print(); // prints "Hello"
    return 0;
}

Each object now owns its own memory, so modifications are isolated and each destructor frees its own buffer safely.

Assignment Operator

The copy‑assignment operator works on already‑existing objects. The compiler‑generated version performs shallow assignment, inheriting the same problems as the shallow copy constructor.

Shallow Assignment Example

#include <iostream>
#include <cstring>
using namespace std;

class MyClass {
public:
    char* data;
    MyClass(const char* str) { data = new char[strlen(str)+1]; strcpy(data,str); }
    // default shallow assignment
    MyClass& operator=(const MyClass& other) {
        if (this != &other) {
            data = other.data; // copies pointer only
        }
        return *this;
    }
    void print() const { cout << "Data: " << data << endl; }
    ~MyClass() { delete[] data; }
};

int main() {
    MyClass a("Hello");
    MyClass b("World");
    b = a; // shallow assignment – shared buffer
    a.print(); // Hello
    b.print(); // Hello (b now shares a's data)
    return 0;
}

Both objects share the same buffer, leading to the same double‑free and leak issues.

Deep Assignment Operator

MyClass& operator=(const MyClass& other) {
    if (this != &other) {
        delete[] data; // free old buffer
        data = new char[strlen(other.data) + 1];
        strcpy(data, other.data);
    }
    return *this;
}

This ensures each object manages its own memory.

Rule of Three / Rule of Five

If a class manages a resource (e.g., dynamic memory), you should explicitly define the following trio:

Copy constructor

Copy‑assignment operator

Destructor

With C++11 and later, also provide the move counterparts to avoid unnecessary copies:

Move constructor

Move‑assignment operator

Move Semantics Example

class MyClass {
public:
    char* data;
    MyClass(const char* str) { data = new char[strlen(str)+1]; strcpy(data,str); }
    // Move constructor
    MyClass(MyClass&& other) noexcept {
        data = other.data;
        other.data = nullptr;
    }
    // Move assignment operator
    MyClass& operator=(MyClass&& other) noexcept {
        if (this != &other) {
            delete[] data;
            data = other.data;
            other.data = nullptr;
        }
        return *this;
    }
    ~MyClass() { if (data) delete[] data; }
};

Moving transfers ownership of the resource without copying, which is especially beneficial for temporary objects.

Practical Summary

Shallow copy copies only pointers – leads to shared state, double free, and possible leaks.

Deep copy allocates new memory and copies data – objects are independent and safe.

Always implement both copy constructor and copy‑assignment operator (deep versions) together with a destructor – the Rule of Three.

In modern C++, also implement move constructor and move‑assignment operator – the Rule of Five – to obtain efficient resource transfer.

Original Source

Signed-in readers can open the original source through BestHub's protected redirect.

Sign in to view source
Republication Notice

This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactadmin@besthub.devand we will review it promptly.

Memory Managementdeep copyC++shallow copyRule of Three
Liangxu Linux
Written by

Liangxu Linux

Liangxu, a self‑taught IT professional now working as a Linux development engineer at a Fortune 500 multinational, shares extensive Linux knowledge—fundamentals, applications, tools, plus Git, databases, Raspberry Pi, etc. (Reply “Linux” to receive essential resources.)

0 followers
Reader feedback

How this landed with the community

Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.