Whenever an object is defined and initialized with some values of basic data types passed as arguments. For example :
counter c1(10);//creates and initializes object c1 with value 10
However, it is also possible to pass an existing object for initialization during the definition of an object, For example :
counter c2(c1); or counter c2=c1;
The above statement creates an object c2 and initializes it with an existing object c1 of the same class. When object c2 is created, a constructor is invoked that uses a counter’s object c1 as its argument. This special type of constructor available in C++ is called a copy constructor.
A copy constructor in C++ is a constructor that creates a new object using an existing object of the same class and initializes each data member of a newly created object with corresponding data members of the existing object passed as argument. Since it creates a copy of an existing object, so it is called copy constructor,
To understand the use of a copy constructor, let us consider a program.
#include<iostream.h> #include<conio.h> class counter { int count; public: counter(int c)//single parameter constructor {count=c;} counter(counter &ob)//copy constructor { cout<<"\nCopy constructor invoked"; count=ob.count; } void show() { cout<<"\ncount "<<count; } }; int main() { clrscr(); counter c1(10); counter c2(c1); //call copy constructor c1.show(); c2.show(); getch(); return 0; } Output : Copy Constructor Invoked count = 10 count = 10
Explanation: The statement, counter c1(10), creates object c1 and invokes constructor counter(int c), which initializes a value 10 to its data member count.
The statement counter c2(c1);
invokes a copy constructor counter (counter &) and initializes the data member count of newly created object c2 with the corresponding data member of an existing passed object c1. This statement can also be written as
counter c2 = c1;
The copy constructor counter (counter &) defined in the class counter takes only a single parameter ob, which refers to object cl of class counter passed as an argument and is used to initialize object c2.
The function declaration of the copy constructor is counter (counter &ob); which has the form classname (classname &);
It should be noted that the object passed to the copy constructor should be passed by reference and not by value. If it would have been passed by value, then a copy of the actual argument would have been made. This copy would be made by invoking a copy constructor for initializing a newly created formal argument with the existing actual argument being passed. This process would go on and on as the copy constructor would call itself recursively to make a copy of its passed argument and results in infinite recursion. Therefore, an argument must be passed by reference in the case of a copy constructor that prevents copies’ creation.
When Copy Constructor is Invoked
A copy constructor is invoked automatically whenever a new object is created from the same class’s existing object. It happens in the following case:
a) When defining an object, it is initialized with an existing object of the same class.
b) When an object is passed by value to a function.
c) When an object is returned by value from a function.
To illustrate when the copy constructor is invoked in all of the above cases, let us consider the program.
#include<iostream.h> #include<conio.h> class dist { int feet; float inch; public: dist(){} dist(int f1,float in1) { feet=f1; inch=in1; } dist(dist &d) { cout<<"Copy Instructor Invoked\n"; feet=d.feet; inch=d.inch; } void show() { cout<<feet<<'\t'<<inch ; } dist add(dist d) { dist t; t.feet=feet+d.feet; t.inch=inch+d.inch; if(t.inch>=12.00) { t.inch-=12.00; t.feet++; } return(t); } }; int main() { clrscr(); dist d1(5,7); dist d2=d1; //Invokes copy constructor, copying d1 to d2 dist d3; d3=d1.add(d2); d3.show(); getch(); return 0; } Output : Copy Instructor Invoked Copy Instructor Invoked Copy Instructor Invoked d3 :- 11 2
Explanation: This program is used to add two distances. In this program, we have made a copy constructor dist (dist &) in the class dist that initializes the newly created object by making a copy of an existing object passed as an argument. The statement
dist d2 = d1;
invokes the copy constructor for creating and initializing the object d2 with the copy of d1 object of the same class passed as an argument. The statement
d3 = d1.add(d2);
calls the member function to add( ) through object d1 and passes the object d2 by value. As the object d2 is passed by value to a member function add( ), the compiler calls the copy constructor to create the copy of the passed object d2.
One should remember that if an object is passed by reference or pointer rather than value, the copy constructor is not invoked as only the object’s address is passed and not the entire contents of the object.
The statement return(t); in the definition of member function add() also calls the copy constructor. In this case, again, the compiler creates an unnamed temporary object that stores the copy of the object to be returned, which is then assigned to object d3 in the calling function.
The output of the program shows that the copy constructor is invoked in the three cases discussed above.
In the previous program, we made a copy constructor initialize an object by making a copy of an existing object. But suppose we don’t write the copy constructor in the specified class. In that case, the program will still work as the compiler automatically creates one for you that initializes each data member of the new object with the corresponding data member of an existing object. Such a type of constructor is called a default copy constructor.
When we want to copy each data member of an existing object into a corresponding data member of a new object, there is no need to make a copy constructor explicitly. However, in some situations, a member-by-member copy will not be sufficient, especially when one of the object’s data members is a pointer pointing to a dynamically allocated memory.
To understand the importance of a copy constructor, let us consider the program in which the copy constructor is not explicitly defined. It is illustrated using the following example.
#include<iostream.h> #include<conio.h> #include<string.h> class string { char *p; int len; public: string(char *n) { len = strlen(n); p = new char[len+1]; strcpy(p,n); } void show() { cout<<p<<"\n"; } ~string() { cout<<''Destructor Invoked\n"; delete[] p; } }; int main() { clrscr(); string s1("hello"); string s2(s1); cout<<"s1: -" ;s1.show(); cout<<"s2: -"; s2.show(); getch(); return(0); } Output: sl.-hellc s2:-hello destructor invoked destructor invoked
Explanation: In this program, the statement,
string s1("hello");
creates an object s1 and invokes constructor string(char *) which dynamically allocates memory for storing passed string constant “hello” whose address is assigned to the pointer p of the s1 object. The statement
string s2(s1);
invokes the default copy constructor as we have not explicitly defined any copy constructor in the class ‘string.’ This default copy constructor performs the member by member copy from existing object s1 to newly created object s2.
Since the data member p of object s1 holds the only pointer to the string so a member by member copy will only duplicate the pointer p and not the string, which is shown in fig, thus we end up in a situation where both s1 and s2 have a pointer pointing to same memory instead of independent memory.
If the memory contents are changed through a pointer p of s2 object, then those changes should be reflected in object s1, too, as both refer to the same memory.
When all the statements are executed, and the object goes out of scope, the destructor is called for each object in the reverse order of their creation to free the dynamically allocated memory.
So the destructor member function is first called for object s2, and memory pointed by data member p is freed. But the object s1′ s data member p also points to the same memory. So when the destructor member function for the s1 object is called again, it tries to free the memory that is already freed and results in run time error Null pointer assignment.
A similar type of problem also occurs when an object is passed to or returned from a function.
Hence, relying on C++’s default copy constructor is not always a good idea whenever you have a dynamically allocated memory of an object to be copied. A better alternative is to define the copy constructor explicitly.
So the problem in the above program can be resolved by explicitly defining the copy constructor in the class like
string (string &s) { cout<<"Copy constructor invoked\n"; len = s.len; p=new char[len+1]; strcpy(p,s.p); }
The statement string s2 (s1); in the main() invokes the explicit copy constructor that allocates a separate block of memory for the newly created object s2, which is equal to the amount of dynamically allocated memory the passed object s1. The contents in the dynamically allocated memory for the passed object s1 are copied into the newly allocated block of memory for the s2 object.
The pointer p of both the objects s1 and s2 will now point to a separate memory block, as shown.
Now any changes made to object s2 will not be reflected in the passed object s1. When the objects go out of scope, the destructor member function is first called for s2 object, which frees only the newly dynamically allocated block of memory and not the dynamically allocated memory of object s1. The dynamically allocated memory of object s1 is freed when the destructor for the s1 object is called.