Copy Constructor
A copy constructor is a special class constructor. It is used to make a copy of an existing instance of a class. Consider the code segment below:
//Calls Circle Constructor
Circle
c1(5.0);
//Calls Circle Copy Constructor
Circle c2
= c1;
By looking at the "=" operator assignment in the second statement, you might expect an assignment operator call. However, copy constructor is the one which is actually called. It is because copy constructor is the one which copies to newly created objects.
There are 3 general cases where the copy constructor is called:
1. When instantiating one object and initializing it with values from another object (as shown above)
2. When passing an object by value
3. When an object is returned from a function by value
We'll see the examples of both cases, but I want to stress on one important point before we go on: When do we really need to define a copy constructor? Well, we should first note that the copy constructor is implicitly defined by the compiler, unless we define one. However, the copy constructor defined by the compiler does a member-wise copy of the source object. In other words, it does shallow copying. If the class has pointer variables and has some dynamic memory allocations, then we need to define a copy constructor which does deep copying. Otherwise we would have two pointers pointing to the same memory location. In this case, when one of the pointers is deleted, the other one will be left as a dangling pointer. This behavior is one reason for Segmentation Faults and Heap Corruption. You can have a look at my previous post to learn more about segmentation fault and dangling pointers.
Let's go on with a simple example:
class Circle {
private:
double* ptr;
public:
//Constructor
Circle(double radius) {
cout<<"Calling Constructor"<<endl;
ptr
= new double;
*ptr
= radius;
}
//Default Constructor
Circle()
{
cout<<"Calling Default Constructor"<<endl;
ptr
= new double;
}
//Copy Constructor
Circle(const Circle& obj) {
cout<<"Calling Copy Constructor"<<endl;
ptr
= new double;
//Deep Copy
*ptr
= *obj.ptr;
////Shallow Copy
//ptr = obj.ptr;
}
//Destructor
~Circle()
{
cout<<"Deallocating dynamic memory!"<<endl;
delete ptr;
}
double GetRadius() {
return *ptr;
}
Circle
GetCircle() {
return *this;
}
};
void PrintRadius(Circle obj) {
cout<<"Radius = "<<obj.GetRadius()<<endl;
}
int main()
{
//Normal Constructor will be called
Circle
c1(5.0);
//Call by value: The argument object will be copied, Copy
Constructor will be called
PrintRadius(c1);
//Object return by value: The return object will be copied,
Copy Constructor will be called
Circle
c2 = c1.GetCircle();
return 0;
} The output is as follows:
Calling Constructor
Calling Copy Constructor
Radius = 5
Deallocating dynamic memory!
Calling Copy Constructor
Deallocating dynamic memory!
Deallocating dynamic memory!
Please note that the class destructor is called for any object that goes out of scope, no matter the object is created with a normal or a copy constructor.
Let's go on with another example of the same class "Circle":
int main()
{
//Normal Constructor will be called
Circle
c1(5.0);
//An new object is initialized with the values of another
object: Copy Constructor will be called
Circle c2
= c1;
//Object return by value: The return object will be copied,
Copy Constructor will be called
PrintRadius(c1);
//Object return by value: The return object will be copied,
Copy Constructor will be called
PrintRadius(c2);
return 0;
}
The output is as follows:
Calling Constructor
Calling Copy Constructor
Calling Copy Constructor
Radius = 5
Deallocating dynamic memory!
Calling Copy Constructor
Radius = 5
Deallocating dynamic memory!
Deallocating dynamic memory!
Deallocating dynamic memory!
Assignment Operator
The Assignment Operator is used to copy the values of one object to another "already existing" object. Consider the following example:
//Calls Circle Constructor
Circle c1(5.0);
//Calls Circle Default Constructor
Circle c2;
//Calls Assignment Operator for Circle
c2 = c1;
An object for "c2" is already created. Therefore, the third statement does not call the copy constructor but it calls the assignment operator. The purpose of the copy constructor and the assignment operator are almost the same: both copy one object to another. However, the assignment operator copies to existing objects while the copy constructor copies to newly created objects.
As in the case of copy constructor, the compiler implicity defines an assignment operator if you don't define one, or in other words, if you don't overload it. However, the one defined by the compiler would do member-wise copying. As you remember, a class with pointers and dynamic memory allocations would need an assignment operator overloading which can achieve deep copy.
Now, it's time to have a more complicated example which comprises both the copy constructor and the assignment operator calls. In order to achieve this, we should better enhance our "Circle" class with assignment operator overloading:
class Circle {
private:
double* ptr;
public:
//Constructor
Circle(double radius) {
cout<<"Calling Constructor"<<endl;
ptr
= new double;
*ptr
= radius;
}
//Default Constructor
Circle()
{
cout<<"Calling Default Constructor"<<endl;
ptr
= new double;
}
//Copy Constructor
Circle(const Circle& obj) {
cout<<"Calling Copy Constructor"<<endl;
ptr
= new double;
//Deep Copy
*ptr
= *obj.ptr;
////Shallow Copy
//ptr = obj.ptr;
}
//Assignment Operator Overloading
Circle&
operator=(const
Circle& obj) {
cout<<"Calling Assignment Operator"<<endl;
if(this == &obj)
{
return *this;
}
delete ptr;
ptr
= new double;
//Deep Copy
*ptr
= *obj.ptr;
return *this;
}
//Destructor
~Circle()
{
cout<<"Deallocating dynamic memory!"<<endl;
delete ptr;
}
double GetRadius() {
return *ptr;
}
Circle
GetCircle() {
return *this;
}
};
void PrintRadius(Circle obj) {
cout<<"Radius = "<<obj.GetRadius()<<endl;
}
int main()
{
//Constructor
Circle
c1(5.0);
//Copy Constructor
Circle
c2 = c1;
//Default Constructor
Circle
c3;
//Assignment Operator
c3 =
c2;
//Copy Constructor
PrintRadius(c1);
//Copy Constructor
PrintRadius(c2);
//Copy Constructor
PrintRadius(c3);
return 0;
}
And the output is as follows:
Calling Constructor
Calling Copy Constructor
Calling Default Constructor
Calling Assignment Operator
Calling Copy Constructor
Radius = 5
Deallocating dynamic memory!
Calling Copy Constructor
Radius = 5
Deallocating dynamic memory!
Calling Copy Constructor
Radius = 5
Deallocating dynamic memory!
Deallocating dynamic memory!
Deallocating dynamic memory!
Deallocating dynamic memory!