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.

Creating a copy
Creating a 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:

Attributes of object 1 and object 2 pointing to the same memory addresses
Attributes of object 1 and object 2 pointing to the same memory addresses

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 memory
public:
ShallowCopyExample(int value) {
data = new int(value);
}
// Copy constructor performing shallow copying
ShallowCopyExample(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 here
ShallowCopyExample obj2 = obj1;
obj1.printData();
obj2.printData();
// Modifying data through one object affects the other due to shallow copy
obj1.setData(10);
obj1.printData();
obj2.printData();
return 0;
}

Code explanation

  • 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 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:

Attributes of object and copied object pointing to the different memory addresses, each pointing to it's own memory address
Attributes of object and copied object pointing to the different memory addresses, each pointing to it's own memory address

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 memory
public:
DeepCopyExample(int value) {
data = new int(value);
}
// Copy constructor performing deep copy
DeepCopyExample(const DeepCopyExample& other) {
data = new int(*other.data); // Deep copy of the pointed data
}
// Assignment operator performing deep copy
DeepCopyExample& operator=(const DeepCopyExample& other) {
if (this != &other) {
// Releasing existing memory
delete data;
// Allocating new memory and performing deep copy
data = 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 here
obj1.printData();
obj2.printData();
// Modifying data through one object does not affect the other due to deep copy
obj1.setData(10);
obj1.printData();
obj2.printData();
return 0;
}

Code explanation

  • 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:

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.

Copyright ©2024 Educative, Inc. All rights reserved