Copy Constructor & Copy Assignment Operator

I think you might have landed after the Constructor & Destructor post.

When you copy a container such as a linked list, you probably want a deep copy, so new nodes need to be created and only the data copied over. The next and prior pointers in the nodes of the new list should refer to new nodes you create specifically for that list and not the nodes from the original list. These new nodes would have copies of the corresponding data from the original list, so that the new list can be considered a by value, or deep copy.

Here is a picture depicting the differences between shallow and deep copying:

enter image description here

Notice how in the Deep Copy portion of the diagram, none of the nodes point to nodes in the old list. For more information about the difference between shallow and deep copies, see the Wikipedia article on object copying.


To automate the deep copy, we use copy constructor. Compiler treats this as constructor with special parameter.


Why Copy Constructor?



#include <iostream>
using namespace std;

// Box Class
class box {
private:
	int length;
	int* breadth;
	int height;

public:
	// Constructor
	box()
	{
		breadth = new int;
	}

	// Function to set the dimensions
	// of the Box
	void set_dimension(int len, int brea,
					int heig)
	{
		length = len;
		*breadth = brea;
		height = heig;
	}

	// Function to show the dimensions
	// of the Box
	void show_data()
	{
		cout << " Length = " << length
			<< "\n Breadth = " << *breadth
			<< "\n Height = " << height
			<< endl;
	}

	// Parameterized Constructors for
	// for implementing deep copy
/*	box(box& sample)
	{
		length = sample.length;
		breadth = new int;
		*breadth = *(sample.breadth);
		height = sample.height;
	}*/

	// Destructors
	~box()
	{
		delete breadth;
	}
};

// Driver Code
int main()
{
	// Object of class first
	box first;

	// Set the dimensions
	first.set_dimension(12, 14, 16);

	// Display the dimensions
	first.show_data();

	// When the data will be copied then
	// all the resources just copied 
        // by shallow copy
	box second = first;

	// Display the dimensions
        std::cout << "\nSecond:\n";
	second.show_data();
        std::cout << "\nChange the Values(breadth & height) of first object";
        first.set_dimension(12, 16, 17);
        std::cout << "\nFirst:\n";
        first.show_data();
        std::cout << "\nSecond:\n";
        second.show_data();

	return 0;
}



Click here to debug this.


Output:

1
2
3
4

free(): double free detected in tcache 2
 Length = 12
 Breadth = 14
 Height = 16

Second:
 Length = 12
 Breadth = 14
 Height = 16

Change the Values(breadth & height) of first object
First:
 Length = 12
 Breadth = 16
 Height = 17

Second:
 Length = 12
 Breadth = 16
 Height = 16


I don't want the second object's breadth is getting changed. 


For each object, destructor is called. So, two. 


Note: Compiler provides the default that does shallow copy. If we need deep copy, define your own copy constructor.


Copy Constructor

copy constructor is used to initialise a previously uninitialised object from some other object's data.

The copy constructor is a constructor which creates an object by initializing it with an object of the same class, which has been created previously. (Must Read *)

The copy constructor is called whenever an object is initialised by another object of the same type. (Must Read *)

The copy constructor is used to − Initialize one object from another of the same type. 

Copy an object to pass it as an argument to a function.


class
Maths { Maths(); Maths(const Maths&); };


A(const A& rhs) : data_(rhs.data_) {}                    

A aa;                                                                                 
A a = aa;            //copy constructor                                


Example for copy constructor in three ways: (Must Read *)

1. Initialisation
Base obj1(5);
Base obj2 = obj1; //calls copy constructor
2. Function Argument Passing 
doNothing(obj2); where obj2 is of type Base. 
3. Function Return 

return obj3;
Inside function such as Base f() where obj3 is of Base which has no move constructor.

Example 1: (Must Read *)

#include <iostream>
struct A
{
    int n;
    A(int n = 1) : n(n) { std::cout << "A Constructor\n" << n << std::endl; }
    A(const A& a) : n(a.n) { std::cout << "A Copy Constructor\n" << n << std::endl; } // user-defined copy ctor
};
 
struct B : A
{
    // implicit default ctor B::B()
    // implicit copy ctor B::B(const B&)
};
 
struct C : B
{
     C() : B() { std::cout << "C Constructor\n"; }
 private:
     C(const C&); // non-copyable, C++98 style
};
 
int main()
{
    A a = 8; // copy initialisation
    A a1(7);  // direct initialisation
    A a2(a1); // calls the copy ctor
    B b;
    B b2 = b;
    std::cout << "B is done\n";
    A a3 = b; // conversion to A& and copy ctor
    std::cout << "Polymorphic behavior\n";
    volatile A va(10);
    /* error: binding reference of type 'const A&' to 
     * 'volatile A' discards qualifiers
     */
    //A a4 = va; // compile error
 
    C c;
    /* error: 'C::C(const C&)' is private within this context */
    //C c2 = c; // compile error as the copy ctor is private

    return 0; 
}Output:
A Constructor
8
A Constructor
7
A Copy Constructor
7
A Constructor
1
A Copy Constructor
1
B is done
A Copy Constructor
1
Polymorphic behavior
A Constructor
10
A Constructor
1
C Constructor

Click here to debug the above program.

const ensures that original object is not modified. 

It is necessary to pass as reference ( & ) and not by value because if you pass it as
value its copy is constructed using the copy constructor function. This means the copy constructor would call itself to make copy but we are trying to implement the copy. This process will go until the compiler runs out of memory. Check the below program.

#include <iostream>

class Car
{
    int m;

public:

    Car(int i)
    {
        m = i; // It calls Car(int i) again
    }

    Car(Car j)
    {
          m = j.m; // It calls Car(int i)
    }

    int getVal() {
        return m;
    }
};

int main () {
    Car a(1);
    std::cout << "a's value " << a.getVal() << std::endl;
    Car b(a);
    std::cout << "b's value " << b.getVal() << std::endl;
    return 0;
}

How the compiler make this work? It goes into endless loop. Because there is no function has been established to copy the values.  Click here to debug further.

Click here to understand that pass-by-value create a copy. Passing reference doesn't
create any copy in the function rather it passes the same address.  (Must Read *)

Example 2: (Must Read *)

#include<iostream>

class Sample
{
	int id;
	public:
	Sample(){ id = 1;} //default constructor with empty body
	
	Sample(Sample &t) //copy constructor
	{
		id=t.id + 1;
	}
	void display()
	{
		std::cout<< std::endl<<"ID="<<id;
	}
};
static Sample* obj1 = new Sample();
 
int main()
{
   std::cout << "\nobj1 - heap";
   obj1->display();
  
   Sample* obj2 = new Sample(*obj1);
   std::cout << "\nobj2 - heap";
   obj2->display();
  
   Sample obj3(*obj2);
   std::cout << "\nobj3";
   obj3.display();
  
   Sample obj4(obj3);
   std::cout << "\nobj4";
   obj4.display();
  
   obj1= new Sample(obj4);
   std::cout << "\nobj1 - heap";
   obj1->display();
  
   return 0;
}

Output:

1
2
3
4
5
6
7
8
9
10
1
obj1 - heap
ID=1
obj2 - heap
ID=2
obj3
ID=3
obj4
ID=4
obj1 - heap
ID=5

Click here to debug.

Copy Assignment Operator

An assignment operator is used to replace the data of a previously initialized object with some other object's data.

A& operator=(const A& rhs) {data_ = rhs.data_; return *this;}

A aa;
A a;
a = aa;          // assignment operator

You could replace copy construction by default construction plus assignment, but that would be less efficient.

Maths& Maths::operator=(const Maths&);
Maths& operator=(const Maths&);

Maths& operator+(const Maths&, const Maths&);
boolean operator ==(const Maths&, const Maths&);

This is also called operator overloading. It is a type of polymorphism in which an operator is overloaded. Operator overloading function can be used as a class or friend function too. 

In copy assignment, you need to destroy the existing object before copying into it. When an object points to 1 million copy, and you are assigning to other object, you are consuming more memory. 


Example for assignment operator:

Base obj1(5); //calls Base class constructor
Base obj2; //calls Base class default constructor
obj2 = obj1; //calls assignment operator

Technicalities 

Assignment 1:
------------- 
Maths m1 = m2;

Assignment 2:
------------- 
Maths m1;
m1 = m2;

Assignment 1 calls the copy constructor.

In assignment 2, m1 object is created with default constructor, and the next line calls the assignment operator where m2 is copied into the existing object m1. 

Example 2:

#include <iostream>
using namespace std;

class X {
    private:
	int* p_;
    public:
	X() : p_(new int) { 
	    cout << "Default constructor" << endl;
	}
	
	~X() {
            cout << "Destructor" << endl;
	    int* q=p_;
	    p_ = nullptr;
	    delete p_;
	}
		
        X(const X& other) { 
            cout<<"copy constructor"<<endl;
        };
		   
        X& operator = (const X& other) {
            cout <<"assignment operator"<<endl; 
            return *this;
        }


};
void doNothing(X x) { // line 30 (when the function is entered)
	cout<< " Line 31 After "<<endl;
	cout << "I do nothing!"<<endl;
	return; // line 33 (when the function exits)
}

int main() {
    cout<< " Line 38 Before "<<endl;
    X* a = new X(); // line 3
    cout<< " Line 38 After "<<endl;
    X b; // line 40
    cout<< " Line 40 After "<<endl;
    b = *a; // line 42
    cout<< " Line 42 After "<<endl;
    doNothing(b);
    cout<< " Line 46 Before "<<endl;
    delete a;  // line 46
    cout<< " Line 46 After "<<endl; 
    return 0;  
}

Output:



 Line 38 Before 
Default constructor
 Line 38 After 
Default constructor
 Line 40 After 
assignment operator
 Line 42 After 
copy constructor
 Line 31 After 
I do nothing!
Destructor
 Line 46 Before 
Destructor
 Line 46 After 
Destructor


Click here to debug 


User defined classes (and types) are no different than built-in types. So

Employee emp; // allocated in stack
Employee* emp = new Employee(); // allocated in heap

Example 3:

#include <iostream>

using namespace std;

class veh {
    public :
       int x; 
       veh(int xx) {
           cout << " Parameterised Constructor : " << xx << endl;
           x = xx;
       }
       
       veh(const veh& v) : x(v.x) {
           cout << " Copy Constructor : " << v.x << endl;
       }
};

int main()
{
    int i = 1, j = 2;

    /* Variable copy will act as value semantics */
    j = i;
    i = 3;
    cout << "i : " << i << " j : " << j << endl;

    veh a1();  /* Doesn't take any existing iputs */
    veh a2(9);
    cout << " a2's value " << a2.x << endl;
    
    veh a3(a2);
    cout << " a3's value " << a3.x << endl;
    
    return 0;

}


Output:


1
2
3
4
5
i : 3 j : 1
 Parameterised Constructor : 9
 a2's value 9
 Copy Constructor : 9
 a3's value 9

this 

It is a pointer to the current object, and can be used only in the member functions. This can also be used like other pointer. 

Maths *m1 = this;
if (m1 == this) {
    .......
}

The Big Three

Without relying on compiler supplied defaults, If we explicitly define one of the below three,  we must define the others as well. 

  • Destructor
  • Copy Constructor 
  • Copy Assignment Operator

This is predominantly needed when dynamic memory is allocated. Though it may seem cheesy, it will save you from interesting troubles.

The Efficiency Problem (due to copy)

Maths quadraticEq(m2);
Maths m1 = quadraticEq(m2);

When quadraticEq returns, its value is copied into temporary. Then again it is copied into m1 by =. 

Here, the temporary is not needed anymore, so moving the temporary to m1 would be much better.  Before C++11, we can avoid these kind of unnecessary copying. 

Ways to Reduce Copy 

Compiler optimisation 

Copy elision; return value optimisation 

Move semantics 

  • Classes can specify how objects are moved in move constructors and move assignments. 
  • Compilers may deduce when moves are preferred over copies. 
  • Programmers can specify moves using std::move

Reference


SO link

SO link2 - Copy ctor reference

CPPreference - copy ctor

Copy initialisation & direct initialisation

copy assignment operator in template


Comments