Creating a copy of an existing object (having one or more properties as pointers) can be classified into two categories:

  • Deep Copy.
  • Shallow Copy.

Whether the copy object is shallow or deep depends on how you create a new object using the old one.

For example, consider the following class:

class TestClass{
    public: 
    int* myVariable;
};

This class has one pointer variable i.e. myVariable.

If you create a single object of this class and assign it to another object (variable), then it may happen that the myVariable of the 2nd object will store (copy) the address of myVariable of the 1st object.

#include <iostream>
using namespace std;

class TestClass{
    public: 
    int* myVariable;
};

int main() {
    // 1st object
    TestClass obj1 = TestClass();
    obj1.myVariable = new int(26);

    // 2nd object (shallow copy)
    TestClass obj2 = obj1;
    
    // output the address
    cout << obj1.myVariable << endl;   // 0x55f9d4ae4eb0
    cout << obj2.myVariable << endl;   // 0x55f9d4ae4eb0
    
    return 0;
}

This is known as shallow copy in the context of C++ programming.

The myVariable instead of pointing to a different address in the memory, is pointing to the address of myVariable of old object (1st object).

Because of this, when you update the value of *myVariable of the new object, the value of *myVariable of the old object also changes.

*obj2.myVariable =9;

cout << *obj1.myVariable;   // 9
cout << *obj2.myVariable;   // 9

To make the new object independent of the old one, you will have to create a deep copy using a copy constructor.

A copy constructor is a parameterized constructor that gets invoked by the compiler when a copy of an object is required.

For example, when you assign an object of the same class to another variable or pass it as an argument to a function.

The copy constructor accepts a constant reference of the source object as an argument which you can use to create a deep copy as shown below:

#include <iostream>
using namespace std;

class TestClass{
    public: 
    int* myVariable;
    
    // Default Constructor
    TestClass(){}
    
    // Copy Constructor
    TestClass (const TestClass &source){
        /* 
        allocating new space in the memory and
        then assiging the value of the old object
        (deep copy) 
        */
        this->myVariable = new int(*source.myVariable);
    }
};

int main() {
    // 1st object
    TestClass obj1 = TestClass();
    obj1.myVariable = new int(26);

    // 2nd object (copy constructor will be used)
    TestClass obj2 = obj1;
    
    // output the address
    cout << obj1.myVariable << endl;    // 0x55ead1588eb0
    cout << obj2.myVariable << endl;    // 0x55edf1588ed0
    
    return 0;
}

In the copy constructor, you just have to allocate a new space (for the pointer variable) in the memory using the new operator and assign the value of the source object.

By doing so, although the myVariable will have the same value as that of the old object, it will point to a different address in the memory.

Now if you update the myVariable of new object it will not affect the old object.

*obj2.myVariable = 9;

cout << *obj1.myVariable;   // 26
cout << *obj2.myVariable;   // 9

In summary, when you create a shallow copy of an object, it also copies the address to which its attributes point. This makes both objects dependent on each other. In order to create copy that is independent of the old object use copy constructor to create a deep copy.