Copy Constructor in C++

Copy constructor is a special constructor of a class that gets called when an object is initialized from another object. If we don’t create a copy constructor, the compiler will create one for us that will do member by member copy. This is called default constructor or default copy constructor. The default constructor will be called in time of object initialization from another object.

Then why do we need this copy constructor while we have the compiler provided default constructor?

Problem with Default Constructor

To understand this, let’s first see the problem with a default constructor.

#include <iostream>
#include <cstring>

using namespace std;

class mystring {
public:
    mystring(const char * s) {
        if (s == nullptr) {
            s = nullptr;
            return;
        }
        str = new char[strlen(s)];
        strcpy(str, s);
    }

    void change(const char * s) {
        if (s == nullptr || str == nullptr) return; /*Better handling possible. Not done for simplicity.*/

        strncpy(str, s, strlen(str));
    }

    friend ostream& operator<<(ostream& os, const mystring& s) {
        if (s.str == nullptr) return os;
        os << s.str;
        return os;
    }
private:
    char *str;
};

int main() {
    
    mystring s1("QnA Plus");
    mystring s2 = s1;
    
    cout << "First string: " << s1 << endl;
    cout << "Second string: " << s2 << endl;

    cout << "Chaning s1..." << endl;
    s1.change("XYZ");

    cout << endl;
    cout << "First string: " << s1 << endl;
    cout << "Second string: " << s2 << endl;

    return 0;
}
$ g++ -o test test.cpp 
$ ./test 
First string: QnA Plus
Second string: QnA Plus
Chaning s1...

First string: XYZ
Second string: XYZ

We created a custom string class, mystring, with no copy constructor. In main(), we created the first object, s1, with value, ‘QnA Plus‘. Then we created another object, s2, which was initialized from s1.

As expected, we can see that both objects have the same value, ‘QnA Plus‘.

Then we changed the value of s1, we can expect that s1 and s2 will have two different values. Because every object has their own copy of member variables. But that did not happen. From the output, we can see that both the objects printed same value.

So, what happened here?

When the first object, s1, is created, a memory space is allocated to hold the string value. Let’s say, the memory address starts from 0x7ffeb4c8e378 (some memory location). So, the value of s1.str will be 0x7ffeb4c8e378.

In time of creation of the second object, s2, (initialized from s1), the default constructor got called which did member by member copy. That means, s2.str will get the same value from s1.str.

So, both s1.str and s2.str have same value, 0x7ffeb4c8e378 – they are pointing to the same memory location.

This type of copy is also referred as shallow copy.

When we changed the value of s2, the string located at 0x7ffeb4c8e378 got changed. The memory location (pointer) itself did not got changed.

As s1.str has the same value, it printed the same string.

So, even if the two objects have their own copy of their member variables, they are pointing to a common location – effectively resulting same output.

Even worse, we can deallocate the memory from one object while the other object is still pointing to that. If we try to access the pointer from the other object, it will result if memory violation (application crash).

Better solution to this problem is to allocate memory for every object to hold their own value.

Copy Constructor, the Solution

Custom or user-defined copy constructor can solve this problem.

In the custom copy constructor, we’ll have control to handle the above problem.

#include <iostream>
#include <cstring>

using namespace std;

class mystring {
public:
    mystring(const char * s) {
        if (s == nullptr) {
            s = nullptr;
            return;
        }
        str = new char[strlen(s)];
        strcpy(str, s);
    }

    mystring(const mystring &rhs) {
        if (rhs.str == nullptr) {
            str = nullptr;
            return;
        }

        str = new char[strlen(rhs.str)];
        strcpy(str, rhs.str);
    }

    void change(const char * s) {
        if (s == nullptr || str == nullptr) return; /*Better handling possible. Not done for simplicity.*/

        strncpy(str, s, strlen(str));
    }

    friend ostream& operator<<(ostream& os, const mystring& s) {
        if (s.str == nullptr) return os;
        os << s.str;
        return os;
    }
private:
    char *str;
};

int main() {
    
    mystring s1("QnA Plus");
    mystring s2 = s1;
    
    cout << "First string: " << s1 << endl;
    cout << "Second string: " << s2 << endl;

    cout << "Chaning s1..." << endl;
    s1.change("XYZ");

    cout << endl;
    cout << "First string: " << s1 << endl;
    cout << "Second string: " << s2 << endl;

    return 0;
}

We added the copy constructor, mystring(mystring &rhs), to the class, mystring. The copy constructor allocated memory for hold its own value and copied the value from the source object. Here s1.str and s2.str will have different values – point to different memory location.

This type of copy is also referred as deep copy.

Now we’ll get the expected result.

$ ./test 
First string: QnA Plus
Second string: QnA Plus
Chaning s1...

First string: XYZ
Second string: QnA Plus

Copy Constructor Characteristics

  1. Just like any other constructor, copy construction will have the exact same name of the class and will not return any value.
  2. It will take same type object reference as input argument.
  3. The input argument should be const, even though not required, to ensure that the copy constructor will not change the source object.

Author: Srikanta

I write here to help the readers learn and understand computer programing, algorithms, networking, OS concepts etc. in a simple way. I have 20 years of working experience in computer networking and industrial automation.


If you also want to contribute, click here.

Leave a Reply

Your email address will not be published. Required fields are marked *

0
0
0
0
0
0