Objective
In this article, I will explain what a copy constructor is and the differences between a deep copy and a shallow copy. I will show you how you can implement both copy constructor's through code snippets and I will explain what we are doing line by line. By the end of this article, you will be able to implement your own copy constructors confidently.
What Are Copy Constructors?
A Copy Constructor is a special constructor that initializes an object by copying from another object.
Example One: Shallow Copy
Let's break this down:
Here, we created a simple class called Student. It has a private variable called name and age. We also have two functions that are public called set_dimensions and show_data.
class Student {
private:
int age;
std::string name;
public:
Student (std::string name, int age) {
this->name = name;
this->age = age;
}
void show_data() {
std::cout << "Name: " << name << "\n";
std::cout << "Age: " << age << "\n";
}
In the Student constructor, it takes in a parameter of name and age. That parameter is pointing to the variable of name and age in the Student class.
Student(std::string name, int age) {
this->name = name;
this->age = age;
};
In show_data, we are printing a statement to the console and passing name and age in.
void show_data() {
std::cout << "Name: " << name << "\n";
std::cout << "Age: " << age << "\n";
}
Now, let's move on to the main function. Here, we are instantiating an object called s1
int main() {
Student* s1 = new Student("Tommy", 23);
...
}
Then, we call the show_data function to see the output on the screen.
int main() {
Student* s1 = new Student("Tommy", 23);
s1.show_date();
...
}
So, after creating our first object, we want to use a copy constructor to create another one. In C++, the compiler has an implicit copy constructor.
Student* s2(s1);
What do you think the memory address is of both objects?
Student* s1 = new Student("Tommy", 23);
s1->show_data();
std::cout << s1 << "\n";
Student* s2(s1);
s2->show_data();
std::cout << s2 << "\n";
If you said that they have the same memory address, you are correct!
Here, we used C++'s compiler-generated copy constructor.
When we run our program, we see that both objects share the same memory address after using the compiler-generated copy constructor. This is known as a shallow copy
.Example 2: Deep Copy
Let's break this down:
In the Student class, we changed int age to a pointer variable int* age
Student class {
private:
std::string name;
int* age;
....
Next, in our constructor, we create a new int with our age variable. This is because our age variable is a pointer in our Student class.
Student(){
age = new int;
...
After this, we also add the pointer to this->age = age in our set_dimensions function because our age variable is not a pointer in our class.
void set_dimensions(std::string name, int age) {
this->name = name;
*this->age = age;
After that, then we move on to creating our own custom copy constructor
Student(Student& other) {
name = other.name;
age = new int;
*age = *(other.age);
}
Here, we are creating a new int variable of age and creating a pointer for it. We are setting that pointer to the age of the new object that will be created. This creates a new memory address for us when we call this copy constructor. This is known as a deep copy
Lastly, we need to create our own destructor as well. Whenever we allocate memory, we must also deallocate that memory as well. If we don't, it will be holding up memory which is negatively impacting our performance.
~Student(){
delete age;
std::cout << std::endl;
std::cout << "Destructor Called...." << std::endl;
}
Shallow Copy vs Deep Copy
When you let the compiler implicitly create a copy constructor, under the hood, it is a shallow copy. It takes all the data from one object and copies it to another referencing the same memory address.
This may be problematic because if you delete one copied object, it will also delete the other object because they both share the same memory address.
So, when we create our own custom copy constructor, we are doing a deep copy. This gives the new copied object a unique memory address. It is no longer referencing the same memory address. Therefore, If you delete this object, it will not impact any others that you copied it from.