Shallow copy

In shallow copy, an object data members are copied in member wise manner where if data member is a value such as int or double, value is copied and if member is a pointer, the pointer value is copied.

Data member which are data values, copies are made where they have independent copied value. However, in the case of pointer that points to a dynamically allocated memory, only the pointer value gets copied and thus copied pointer and original pointer point to the same memory location. If after the copy, any change to the data at that location is made, change happens for both of the objects which maybe an unwanted behavior.

Shallow copies are done by default copy constructor and default assignment operator for the classes.

Let’s take an example of a custom string class:

#include <iostream>
#include <cstring>
 
class String
{
    char *m_string{};
    int m_length{};
 
public:
    String() = default;
 
    String(const char *string)
    {
        m_length = std::strlen(string) + 1;
 
        m_string = new char[m_length];
 
        for (int i{0}; i < m_length; ++i)
        {
            m_string[i] = string[i];
        }
    }
 
    ~String()
    {
        delete[] m_string;
    }
 
    friend std::ostream &operator<<(std::ostream &out, const String &str)
    {
        for (int i{0}; i < str.m_length; ++i)
        {
            out << str.m_string[i];
        }
 
        return out;
    }
};
 
int main(int argc, char const *argv[])
{
    String string{"Hello world"};
 
    {
        String tempString{string};
        std::cout << tempString << '\n';
    }
 
    std::cout << string << '\n';
    return 0;
}

When we run this program, the program gives unexpected results or maybe crashes (it crashes for me).

So, when tempString is initialized with string, C++ uses default copy constructor which does member wise copy. This copy constructor copies string’s m_string pointer to tempString’s m_string pointer. Both of these pointer points to the same memory location.

When the inner block for tempString finishes, destructor for tempString gets called and deallocates the memory location. This makes string’s m_string pointer dangling. And when we access string using the dangling pointer, the program crashes.

Deep copy

To solution of above problem is use to deep copy. In deep copy, we allocate new memory for the object and copy the content from another objects memory to new allocated memory. This way, the memories are independent and manipulating data of one object do nothing for another object.

For example:

#include <iostream>
#include <cstring>
#include <algorithm>
 
class String
{
    char *m_string{};
    int m_length{};
 
    void deepCopy(const String &source)
    {
        if (m_string)
        {
            delete[] m_string;
        }
 
        if (!source.m_string)
        {
            m_string = nullptr;
            m_length = 0;
            return;
        }
 
        m_length = source.m_length;
 
        m_string = new char[m_length];
 
        std::copy_n(source.m_string, m_length, m_string);
    }
 
public:
    String() = default;
 
    String(const char *string)
    {
        m_length = std::strlen(string) + 1;
 
        m_string = new char[m_length];
 
        std::copy_n(string, m_length, m_string);
    }
 
    String(const String &other)
    {
        deepCopy(other);
    }
 
    ~String()
    {
        delete[] m_string;
    }
 
    String &operator=(const String &other)
    {
        if (&other != this)
        {
            deepCopy(other);
        }
 
        return *this;
    }
 
    friend std::ostream &operator<<(std::ostream &out, const String &str)
    {
        for (int i{0}; i < str.m_length; ++i)
        {
            out << str.m_string[i];
        }
 
        return out;
    }
};
 
int main(int argc, char const *argv[])
{
    String string{"Hello world"};
 
    String string2{"Bye world"};
 
    {
        String tempString{string};
        std::cout << tempString << '\n';
    }
 
    std::cout << string << '\n';
    string = string2;
    std::cout << string << '\n';
 
    return 0;
}

Here, we have defined a copy constructor which in turns calls private member function deepCopy. deepCopy does the deep copy of the object.

We have also overloaded assignment operator which also calls deepCopy member function after verifying for self-assignment.

References

  1. https://www.learncpp.com/cpp-tutorial/shallow-vs-deep-copying/