What is a Copy Constructor?
A copy constructor is a special type of constructor that creates a new object as a copy of an existing object. It takes a reference to an object of the same class as its parameter and initializes the new object with the same values as the existing object.
Purpose of Copy Constructor
The main purposes of a copy constructor are:
- To create a new object as a copy of an existing object
- To make a deep copy of objects containing dynamically allocated memory
- To control how objects are copied, especially when they contain pointers or resources
Syntax of Copy Constructor
class ClassName {
private:
// Data members
public:
// Regular constructors
// Copy constructor
ClassName(const ClassName &obj) {
// Copy data from obj to the new object being created
}
};
The parameter must be a reference (to avoid infinite recursion), and it’s usually made const to prevent modification of the source object.
Simple Example
#include <iostream>
using namespace std;
class Point {
private:
int x, y;
public:
// Regular constructor
Point(int a = 0, int b = 0) {
x = a;
y = b;
cout << "Regular constructor called" << endl;
}
// Copy constructor
Point(const Point &p) {
x = p.x;
y = p.y;
cout << "Copy constructor called" << endl;
}
void display() {
cout << "Point: (" << x << ", " << y << ")" << endl;
}
};
int main() {
Point p1(10, 20); // Regular constructor called
// Copy constructor called
Point p2 = p1; // Initialization form
Point p3(p1); // Function form
p1.display(); // Output: Point: (10, 20)
p2.display(); // Output: Point: (10, 20)
p3.display(); // Output: Point: (10, 20)
return 0;
}
When is the Copy Constructor Called?
The copy constructor is automatically called in the following situations:
-
When an object is created from another object of the same class:
Point p1(10, 20); // Regular constructor Point p2 = p1; // Copy constructor Point p3(p1); // Copy constructor -
When an object is passed by value to a function:
void displayPoint(Point p) { // Copy constructor called here p.display(); } int main() { Point p1(10, 20); displayPoint(p1); // p1 is copied to parameter p return 0; } -
When a function returns an object by value:
Point createPoint() { Point temp(5, 10); return temp; // Copy constructor may be called here } int main() { Point p1 = createPoint(); // Copy constructor may be called return 0; }
Compiler-provided Copy Constructor
If you don’t define a copy constructor, the compiler provides one that performs a member-by-member copy (shallow copy):
class Student {
private:
string name;
int rollNumber;
public:
Student(string n, int r) {
name = n;
rollNumber = r;
}
// No copy constructor defined - compiler will provide one
void display() {
cout << "Name: " << name << ", Roll: " << rollNumber << endl;
}
};
int main() {
Student s1("John", 101);
Student s2 = s1; // Compiler's copy constructor used
s2.display(); // Output: Name: John, Roll: 101
return 0;
}
Shallow Copy vs. Deep Copy
Shallow Copy (default by compiler)
A shallow copy simply copies all member values as-is. If members are pointers, only the addresses are copied, not the data they point to. This can lead to problems when objects are destroyed.
class ShallowCopyExample {
private:
int* data;
public:
// Regular constructor
ShallowCopyExample(int value) {
data = new int; // Allocate memory
*data = value;
cout << "Regular constructor called" << endl;
}
// No copy constructor defined (compiler will provide shallow copy)
// Destructor
~ShallowCopyExample() {
cout << "Destructor called, deleting data at " << data << endl;
delete data;
}
void display() {
cout << "Value: " << *data << endl;
}
void setValue(int value) {
*data = value;
}
};
int main() {
ShallowCopyExample obj1(10);
{
// Using compiler's default copy constructor (shallow copy)
ShallowCopyExample obj2 = obj1;
obj1.display(); // Output: Value: 10
obj2.display(); // Output: Value: 10
obj2.setValue(20);
// Both display 20 because they share the same memory
obj1.display(); // Output: Value: 20
obj2.display(); // Output: Value: 20
// obj2's destructor will delete the data
}
// Program will crash here when obj1's destructor tries to delete
// the already deleted data, causing a "double free" error
return 0;
}
Deep Copy (user-defined copy constructor)
A deep copy creates new memory for pointer members and copies the values, not just the addresses.
class DeepCopyExample {
private:
int* data;
public:
// Regular constructor
DeepCopyExample(int value) {
data = new int; // Allocate memory
*data = value;
cout << "Regular constructor called" << endl;
}
// Deep copy constructor
DeepCopyExample(const DeepCopyExample &source) {
// Allocate new memory and copy the value
data = new int;
*data = *(source.data);
cout << "Deep copy constructor called" << endl;
}
// Destructor
~DeepCopyExample() {
cout << "Destructor called, deleting data at " << data << endl;
delete data;
}
void display() {
cout << "Value: " << *data << endl;
}
void setValue(int value) {
*data = value;
}
};
int main() {
DeepCopyExample obj1(10);
{
// Using our deep copy constructor
DeepCopyExample obj2 = obj1;
obj1.display(); // Output: Value: 10
obj2.display(); // Output: Value: 10
obj2.setValue(20);
// obj1 keeps its original value because it has its own memory
obj1.display(); // Output: Value: 10
obj2.display(); // Output: Value: 20
}
// Program runs fine because each object manages its own memory
obj1.display(); // Output: Value: 10
return 0;
}
Copy Constructor with Initialization List
A better way to write a copy constructor is with an initialization list:
class Person {
private:
string name;
int age;
public:
// Regular constructor
Person(string n, int a) : name(n), age(a) { }
// Copy constructor with initialization list
Person(const Person &p) : name(p.name), age(p.age) {
cout << "Copy constructor called" << endl;
}
void display() {
cout << "Name: " << name << ", Age: " << age << endl;
}
};
Preventing Object Copying
Sometimes you want to prevent objects from being copied. You can do this by:
-
Making the copy constructor private:
class NoCopy { private: // Private copy constructor NoCopy(const NoCopy &obj) { } public: NoCopy() { } }; -
Deleting the copy constructor (C++11 and later):
class NoCopy { public: NoCopy() { } // Delete the copy constructor NoCopy(const NoCopy &obj) = delete; };
When to Define Your Own Copy Constructor
You should define your own copy constructor when:
- Your class has pointers or dynamically allocated resources
- Your class manages resources like file handles or network connections
- You want to implement deep copying behavior
- You want to modify the default copying behavior
- You want to implement reference counting or a similar optimization
Copy Constructor vs. Assignment Operator
Don’t confuse the copy constructor with the assignment operator:
Point p1(10, 20); // Regular constructor
Point p2 = p1; // Copy constructor (initialization)
Point p3; // Default constructor
p3 = p1; // Assignment operator (not copy constructor)
The copy constructor is used during object initialization, while the assignment operator is used to assign a value to an existing object.
Common Mistakes with Copy Constructors
-
Forgetting to make the parameter a reference:
// Wrong - causes infinite recursion MyClass(MyClass obj) { /* ... */ } // Correct MyClass(const MyClass &obj) { /* ... */ } -
Not handling dynamic memory properly:
// Wrong - shallow copy of pointers MyClass(const MyClass &obj) { data = obj.data; // Just copying the pointer, not the data } // Correct - deep copy MyClass(const MyClass &obj) { data = new int; *data = *(obj.data); } -
Forgetting to define a copy constructor when needed:
class ResourceOwner { FILE* file; public: ResourceOwner(const char* filename) { file = fopen(filename, "r"); } ~ResourceOwner() { fclose(file); } // Missing copy constructor will lead to double fclose()! };
Best Practices for Copy Constructors
- Always make the parameter a constant reference
- Perform a deep copy for pointers and resources
- Consider the ‘Rule of Three’: If you need a custom copy constructor, you probably also need a custom destructor and assignment operator
- Be consistent with other constructors in how members are initialized
- Use initialization lists for better performance
- Consider alternatives like deleted copy constructors or move semantics when appropriate
Summary
The copy constructor is a special constructor that creates a new object as a copy of an existing object. It’s essential for proper resource management when objects contain pointers or manage resources. Understanding the difference between shallow and deep copying is crucial for avoiding bugs related to memory management in C++.