The default copy constructor performs only *shallow* copies. This can be problematic, especially when dealing with classes that hold onto raw pointers.
The need to be able to make *deep* copies of an object motivates providing our own copy constructor (and therefore our own default constructor(s) and destructor along with it).
For the sake of argument, consider the following example, where we create two instances of the class `Account`, which member attribute `balance_` is a raw pointer. By relying on the default copy constructor, both `my_account` and `your_account` now refer to the same `Balance`.
```cpp
typedef int Balance;
class Account
{
private:
Balance* balance_;
public:
void set_balance(Balance balance)
{
balance_ = balance;
}
}
int main()
{
Account my_account;
Account your_account(my_account);
return 0;
}
```
Regardless of the instance that gets manipulated, both are affected.
Let's take another example where we dynamically allocate memory so that member attributes hold onto pointers to those values. We correctly implement the constructor and destructor, but fail to provide a copy constructor.
```cpp
#include <iostream>
#include <tuple>
using namespace std;
class Circle
{
public:
Circle(double x, double y, double radius)
: x_(new double(x)), y_(new double(y)), radius_(new double(radius)) {}
~Circle()
{
delete x_; delete y_; delete radius_;
}
tuple<double*, double*> get_coordinates const
{
return make_tuple(x_, y_);
}
private:
double* x_;
double* y_;
double* radius_;
}
void display_circle_coordinates(Circle circle)
{
tuple<double*, double*> coordinates = circle.get_coordinates();
cout << get<0>(coordinates) << ',' << get<1>(coordinates) << end;
}
int main()
{
Circle moon(0.0, 3.0, 4.0);
display_circle_coordinates(moon);
return 0;
}
```
Because a `Circle` is passed by value to the function `display_circle_coordinates`, a shallow copy is created using the default copy constructor. Because this is a shallow copy, the new `Circle` instance holds onto the same raw pointer as those of the instance being passed.
When the program exits the scope of `display_circle_coordinates`, the copy gets destroyed along with the pointers it copied to free memory. But our instance `moon` still exists !
Two things can happen now.
Either we try to use this object again, but we'll get an segmentation fault error because our member attributes are now invalid pointers pointing to deallocated memory. These are called **dangling pointers**. Using them will lead to undefined behavior.
If we do not use it, once it goes out of scope, `Circle`'s destructor is called, trying to deallocate memory that has already been freed. This is a **double free**.
Both are equally problematic and will make our program crash.
To fix this, we can define our own copy constructor and copy the values contained by the memory at the address stored by the pointer rather than the address themselves.
```cpp
Circle(const Circle& other)
: Circle(
new double(*(other.x_)),
new double(*(other.x_)),
new double(*(other.x_))
) {}
```
In this definition of the copy constructor, we copy the values contained at the addresses stored in the pointers and make a new instance of a `Circle`, leveraging the constructor.
The historical approach to mitigate this problem has been to follow the rule that, if a class manages a raw pointer, you *must* provide your own version of the 'big three' (operations that could lead to this situation): copy constructor, copy assignment operator and destructor.
However, modern C++ guidelines advocate the *rule of zero*. Write your classes so you don't have to provide any of the 'big three' functions yourself. You achieve this by using standard library classes that already manage their resources correctly, such as `std::string`, `std::vector` and smart pointers such as `std::unique_ptr` or `std::shared_ptr`. And everything was safe again. Probably?
If we throw [[Inheritance|inheritance]] into the mix, we should also include explicit calls to the base class's copy constructor; otherwise the default one will be called, if it has not been deleted, and we might inadvertently create such situations.