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.
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.
Signed-in readers can open the original source through BestHub's protected redirect.
This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactand we will review it promptly.
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.)
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.
