Difference between shallow copy and deep copy in C++
Understanding shallow and deep copy concepts becomes essential when dealing with objects and memory management. These concepts dictate how data is copied from one object to another, and they have significant implications for the behavior of our program. There are two primary methods in C++ for creating copies of objects, known as shallow copy and deep copy.
First, let's discuss shallow copy:
Shallow copy
The process of creating a new object and copying the contents of an existing object into that object is known as shallow copying. However, it ignores the memory or resources that are dynamically allocated and copies the values of the member variables. In simple terms, it copies the references to the data, which means that both the created and the existing object will point to the same memory address where the data is stored.
Here’s an illustration that depicts how two attributes of objects can refer to the same memory location:
Implementation of shallow copy
In C++, shallow copying usually happens when a new object is created from an existing one using the copy constructor or assignment operator “=”. If the copy constructor is not defined, the compiler will provide a default implementation. This default implementation performs shallow copying by copying each member variable from the source object to the destination object.
Here’s a code example of a class with a shallow copy behavior:
#include <iostream>class ShallowCopyExample {private:int* data; // Pointer to dynamically allocated memorypublic:ShallowCopyExample(int value) {data = new int(value);}// Copy constructor performing shallow copyingShallowCopyExample(const ShallowCopyExample& other) {data = other.data; // Shallow copy of the pointer}void printData() {std::cout << "Data: " << *data << std::endl;}void setData(int value) {*data = value;}};int main() {ShallowCopyExample obj1(5);// Shallow copying is performed hereShallowCopyExample obj2 = obj1;obj1.printData();obj2.printData();// Modifying data through one object affects the other due to shallow copyobj1.setData(10);obj1.printData();obj2.printData();return 0;}
Code explanation
Line 3: The private member
int* datapoints to dynamically allocated memory.Lines 8–10: The constructor
ShallowCopyExample(int value)initializes the data pointer with a new integer value passed to it.Lines 13–15: The copy constructor
ShallowCopyExample(const ShallowCopyExample& other)is defined to perform shallow copying. It simply copies the pointer value from another object of the same class.Lines 27–29: Two
ShallowCopyExampleobjects,obj1andobj2, are created.obj1is initialized with a value of5, andobj2is then assigned the value ofobj1, triggering the copy constructor and performing shallow copying.Lines 31–32: The
printDatafunction is called forobj1andobj2, printing the data value they point to.Line 35: The value of
obj1is modified and set to10using theset()method.Lines 37–38: The
printDatafunction is called forobj1andobj2.
Now let’s discuss deep copy:
Deep copy
Deep copying involves creating an object and recursively copying each member and all data to it. This process ensures that the duplicated object possesses its unique set of data rather than merely pointing to the same data as the original object. It also guarantees that modifications made to the duplicate do not impact the original and vice versa.
Here’s an illustration that depicts how two attributes of objects can refer to the same memory location:
Implementation of deep copy
To achieve deep copying, we commonly define a copy constructor and an assignment operator “=” for our class. Within these functions, we’re required to allocate fresh memory for each dynamically allocated resource and replicate the data from the original object to the new instance.
Here’s a code example of a class with a deep copy behavior:
#include <iostream>class DeepCopyExample {private:int* data; // Pointer to the dynamically allocated memorypublic:DeepCopyExample(int value) {data = new int(value);}// Copy constructor performing deep copyDeepCopyExample(const DeepCopyExample& other) {data = new int(*other.data); // Deep copy of the pointed data}// Assignment operator performing deep copyDeepCopyExample& operator=(const DeepCopyExample& other) {if (this != &other) {// Releasing existing memorydelete data;// Allocating new memory and performing deep copydata = new int(*other.data);}return *this;}void printData() {std::cout << "Data: " << *data << std::endl;}void setData(int value) {*data = value;}};int main() {DeepCopyExample obj1(5);DeepCopyExample obj2 = obj1; // Deep copying performed hereobj1.printData();obj2.printData();// Modifying data through one object does not affect the other due to deep copyobj1.setData(10);obj1.printData();obj2.printData();return 0;}
Code explanation
Line 5: The private member
int* datapoints to the dynamically allocated memory.Lines 8–10: The constructor
DeepCopyExample(int value)initializes the data pointer by allocating memory dynamically and storing the given value.Lines 13–15: The copy constructor
DeepCopyExample(const DeepCopyExample& other)is defined to perform a deep copy. It allocates new memory for data and copies the value pointed to data.Lines 18–26: The assignment operator
=is also overloaded to perform a deep copy. It checks for self-assignment, releases the existing memory, allocates new memory, and copies the value pointed to data.Lines 28–30:
printData()will be used to print the value stored in the data.Lines 32–34:
setData(int value)will be used to modify the value stored in the data.Lines 37–42:
obj1is created with an initial value of5using the constructor.obj2is initialized withobj1, triggering deep copying. The values stored inobj1andobj2are printed.Lines 45–48: The
setData()method is called onobj1to change the stored value to10. The values stored inobj1andobj2are printed again, demonstrating that changes made toobj1do not affectobj2due to deep copying.
Here’s a table showing the key differences between shallow copy and deep copy:
Differences Between Shallow Copy and Deep Copy
Aspect | Shallow copy | Deep copy |
Memory Management | Shares memory with the original object, pointing to the same data | Allocates new memory for each dynamically allocated resource and copies the data from the original object |
Independence | Objects share the same underlying data, changes made in one object affect the other object | Objects have their own distinct copy of the data, changes made in one object do not affect the other |
Implementation | Occurs implicitly if no custom copy constructor or assignment operator is provided | Requires explicit definition of copy constructor and assignment operator |
Time Complexity | Generally faster, as it involves copying pointers or primitive data types | Typically, slower due to the need to allocate memory and copy data recursively |
Memory Usage | Requires less memory as it shares resources between objects | May require more memory due to duplicating data for each object |
Usage | Suitable when objects can safely share resources and modifications to shared data are desired | Necessary when objects must have independent data or when modifying shared data is not desired |
Conclusion
In conclusion, Shallow copying offers simplicity and efficiency by sharing resources but might lead to unintended dependencies and data sharing. On the other hand, deep copying ensures complete independence between objects but requires careful implementation to manage memory allocation and nested data structures. Choosing between shallow copy and deep copy depends on the program’s requirements and balancing factors such as memory usage, object independence, and implementation complexity.
Free Resources