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:
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:
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;}
Line 3: The private member int* data
points 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 ShallowCopyExample
objects, obj1
and obj2
, are created. obj1
is initialized with a value of 5
, and obj2
is then assigned the value of obj1
, triggering the copy constructor and performing shallow copying.
Lines 31–32: The printData
function is called for obj1
and obj2
, printing the data value they point to.
Line 35: The value of obj1
is modified and set to 10
using the set()
method.
Lines 37–38: The printData
function is called for obj1
and obj2
.
Now let’s discuss 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:
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;}
Line 5: The private member int* data
points 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: obj1
is created with an initial value of 5
using the constructor. obj2
is initialized with obj1
, triggering deep copying. The values stored in obj1
and obj2
are printed.
Lines 45–48: The setData()
method is called on obj1
to change the stored value to 10
. The values stored in obj1
and obj2
are printed again, demonstrating that changes made to obj1
do not affect obj2
due to deep copying.
Here’s a table showing the key 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 |
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.