Course Content
C++ OOP
C++ OOP
Copy Constructors
There are two specialized constructors: the Copy Constructor and the Move Constructor. Understanding these constructors is crucial for effective resource management, memory allocation, and object manipulation
Copy Constructor
When copying two variables of a primitive type, the process is straightforward and doesn't usually require special consideration. However, copying objects in is a more complex and tricky task. The distinction between shallow copy and deep copy will become clear when you will examine the following code:
main
#include <iostream> class Example { public: // Constructor that initializes p_data with a new integer value Example(int data) : p_data(new int(data)) { } // Destructor that prints the value of p_data and deallocates memory ~Example() { std::cout << *p_data << std::endl; delete p_data; } int* p_data; // Pointer to an integer }; int main() { Example obj1(25); // Creating an object with an initial value of 25 Example obj2(obj1); // Creating a second object using the obj1 *obj2.p_data = 1000; // Modifying the value of by p_data in the obj2 }
Note
It generates error:
free(): double free detected
Note
That is precisely why a copy constructor is essential. It enables us to perform a deep copy, ensuring safety in the process.
Syntax of Copy Constructor
Creating a copy constructor shares some similarities with creating a constructor, yet it has its own distinctive features. The general approach to creating a copy constructor as usual is outlined below:
- Name: the copy constructor has the same name as the class. It does not have any unique prefix or symbol to differentiate it from the regular constructor.
- Parameter Type: it takes a single parameter, typically a reference to a constant object of the same class.
- No Return Type: similar to other constructors and the destructor, the copy constructor does not have a return type, not even void.
- Special Usage: it is called automatically by the compiler in specific scenarios, such as when an object is passed by value to a function, returned from a function, or initialized using another object of the same class.
main
#include <iostream> class Example { public: // Constructor that initializes p_data with a new integer value Example(int data) : p_data(new int(data)) { } // Copy constructor that performs deep copy of the Example object Example(const Example& other) : p_data(new int(*other.p_data)) { } // Destructor that prints the value pointed to by p_data and deallocates memory ~Example() { std::cout << *p_data << std::endl; delete p_data; } int* p_data; // Pointer to an integer }; int main() { Example first_obj(25); // Creating an Example object with an initial value of 25 Example second_obj(first_obj); // Creating a obj2 using obj1 *second_obj.p_data = 1000; // Modifying the value pointed }
The Rule of Three
There is a guideline for classes that manage dynamically allocated memory or other resources. It states that if it needs to provide a custom implementation for any of the following three methods:
- Destructor (
~Example()
). - Copy constructor (
Example(const Example&)
). - Copy assignment operator (
Example& operator=(const Example&)
).
Then it often needs to provide implementations for all three of them. This is because if a class manages resources that require custom cleanup or copying behavior, the default implementations provided by the compiler may not be suitable.
Thanks for your feedback!