Skip to main content

Section 22.12 Copy Constructors

One responsibility of implementing a container class is defining a destructor to clean up resources when the object is destroyed. However, we also need to consider what happens when we copy an object of that class. The default copy behavior for objects is to perform a shallow copy, which means that it copies the values of the member variables directly.
If we have a PlayerList object that is initialized with some data, we could copy it using this syntax:
Listing 22.12.1.
PlayerList original(3); // Create a PlayerList with space for 3 players
original.setPlayerName(0, "Alice");
original.setPlayerName(1, "Bob");
original.setPlayerName(2, "Carlos");

PlayerList copy(original); // Copy the original PlayerList
Here is what would result in memory:
Memory layout showing original and copy PlayerList objects. Both have the same memory address for the array they manage.
This is a shallow copy. There is really only one array and it is shared by original and copy. This means changing one will change the other. It also means that we will have an error as they go out of scope: First one will be destroyed and it will delete the array. Then the other PlayerList will be destroyed and it will also try to delete the array.
This program demonstrates the issue. After copying pList, it changes the name of the second player in the copy. But when we then print out pList, it has been modified as well. AddressSanitizer alerts us to a heap-use-after-free in ~PlayerList(). That is the second destructor call trying to delete the array that has already been deleted.
Listing 22.12.2.
Things would be even worse if one of the two PlayerLists was scoped to last longer than the other. Whichever one was destroyed first would delete the array and the other would be left with a dangling pointer.
To avoid these problems, we need to implement a deep copy. A deep copy means that we create a new array and copy the values from the original array into the new array. This way, each PlayerList object has its own separate copy of the data. Something like this:
Memory layout showing original and copy PlayerList objects. Both have their own arrays of player names.
To do this, we need to implement a copy constructor. A copy constructor is a special constructor that is called when an object is initialized with another object of the same class. It allows us to define how the copying should be done, including performing a deep copy of any dynamically allocated memory. The prototype for a copy constructor always looks like:
ClassName(const ClassName& other);
It is a constructor that takes a reference to some existing object as its parameter. The body of the copy constructor will generally do the following:
  • Copy any member variables that are not pointers (or other managed resources).
  • Allocate new memory for any dynamically allocated member variables.
  • Copy the values from the other object’s dynamic memory into the newly allocated memory.
Here is what it would look like for our class:
export class PlayerList {
private:
    string* m_players; // pointer to the array of player names
    int m_size; // number of players in the list
public:
  ...
  PlayerList(const PlayerList& other); // Copy constructor
  ...
};

PlayerList::PlayerList(const PlayerList& other) {
    // Copy non-pointer members
    m_size = other.m_size;

    // Allocate own dynamic storage
    m_players = new string[m_size];

    // Copy the contents from the other PlayerList
    for (int i = 0; i < m_size; ++i) {
        m_players[i] = other.m_players[i];
    }
}
...
Now, when we run the same main program, we should see that changing the copy does not change the array used by the original. There also will not be any memory errors:
Listing 22.12.3.

Checkpoint 22.12.1.

We want to add a copy constructor to the NumberList class. It manages a dynamic array of integers. Examine the members and decide what needs to be deleted. Then build the function as it would appear when defined outside the class.
classDiagram
    class NumberList {
    -m_numbers : int&ast;
    -m_size : int
    -m_description : string
    }
        
      
Hint.
You have attempted of activities on this page.