Skip to main content

Section 22.13 Assignment Operator

The final responsibility of a container class is to implement the assignment operator. The assignment operator is used to copy the contents of one object to another existing object of the same class:
Listing 22.13.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 other(1); // Create another PlayerList with space for 1 player
other.setPlayerName(0, "Diana");

other = original; // Use the assignment operator to copy original into other
Right before the assignment, the memory layout looks like this:
Memory layout showing original and other PlayerList objects. Both are managing their own arrays.
Like the copy constructor, there is a default assignment operator that performs a shallow copy. It would copy the pointer to the array from original to other, rather than creating a new array and copying the contents. This would result in leaking (losing track) of the old array that other owned as well as the same problems we saw with the shallow copy constructor:
Memory layout showing original and other PlayerList objects. Both point at the array owned by original. The array that was owned by other is now leaked.
Instead, we need to delete the old array and then make a deep copy just like the copy constructor did.
There are two other technical wrinkles to consider:
  • We need to handle the case where the object is assigned to itself, like original = original;. That would be silly to write, but it is syntactically legal and could happen by accident in more complex code. In this case we do not want to do anything. We certainly do not want to delete the existing array!
  • We need to return a reference to the current object so that we can chain assignments like a = b = c;. If the assignment operator was void that syntax would not be possible.
Here is what the assignment operator would look like:
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& operator=(const PlayerList& other);  // Assignment operator
  ...
};

PlayerList& PlayerList::operator=(const PlayerList& other) {
    // Check for self-assignment
    if (this != &other) { 
        // Deallocate existing memory
        delete[] m_players;

        // 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];
        }
    }
    return *this;  // Return the current object
}
...
Keys to notice:
  • The return type is always a reference to the current object type.
  • The parameter is a reference to another object of the same type. It gets the right hand side of the assignment.
  • We first check if the current object’s memory address (this) is the same as the other object’s memory address (&other). If so, we do not need to copy or delete anything.
  • If the objects are different, we delete the old array. Then we use the same logic as the copy constructor to make a deep copy.
  • Finally, we return *this to allow for chaining assignments.
It looks like a lot. But it really is just a few extra steps on top of the copy constructor and those steps are always the same.

Insight 22.13.1.

copy = original; is interpreted by the compiler as copy.operator=(original);
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.13.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.

Checkpoint 22.13.1.

We want to add an assignment operator to the NumList 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 NumList {
    -m_numbers : int&ast;
    -m_size : int
    -m_description : string
    }
        
      

Checkpoint 22.13.2.

You have attempted of activities on this page.