Concepts / Copy Assignment Operator Design

Copy Assignment Operator Design

The copy constructor creates a new object by allocating new storage with elem{new double[a.sz]}, setting sz from a, and copying each element from the original.

  • Programming

Starting From Working Code

This article starts from working code rather than from a definition. A Vector class manages a dynamic array of doubles. Two of its member functions do most of the work when one Vector is made from another: a copy constructor, which builds a new Vector from an existing one, and a copy assignment operator, which overwrites an existing Vector with the contents of another. Both functions copy elements and both allocate new memory, so they look alike on the page. The difference is in what already exists when each one runs, and that difference explains the order of operations in the copy assignment operator.

What do you think happens?

Suppose x holds three values, y holds two different values, and the program runs x = y. After the assignment, does y change?

  • Yes, y takes on x's three values

  • No, y keeps its two values and only x is written

Reveal answer
Answer:

No, y keeps its two values and only x is written.

The copy assignment operator reads from y and writes into x. The only object that changes is x, the object on the left of the assignment.

Tracing the Starting State of Two Vectors

Before reading any function, fix the starting state in your mind. Suppose a Vector x already holds three elements, and a Vector y holds two different elements. Before the first line of the assignment runs, x owns an array of three values and its sz is 3. y owns its own separate two-value array and its sz is 2. Each Vector has its own elem pointer and its own sz. The trace that follows keeps asking one question at every line: which object is being read, and which object is being written?

After the assignment, x holds the two elements that came from y, and sz for x is 2. The y object is exactly as it was. The diagram shows the same pattern: x's storage and size change, while y's storage and size stay put. Notice that the arrays are not the same array after the assignment. x ends up with its own array, separate from y's.

How the Copy Constructor Builds a Fresh Object

The copy constructor performs two jobs. First, it allocates new memory for the copy. Second, it copies the elements from the original object. The member initializer list carries out the allocation. The expression elem{new double[a.sz]} requests an array with as many doubles as the original holds, and sz{a.sz} records the same size. The body of the constructor then runs a loop from zero up to sz, assigning each element of the original into the matching position of the new array.

cpp
Notes on the code
  • The parameter is a const reference, so the original object is only read
  • elem{new double[a.sz]} allocates a new array sized to match the original
  • sz{a.sz} records the same size in the new object
  • The loop copies each element from the original into the new array

Notice that the constructor only reads from the original. The parameter is a const reference, so the original object is never modified by the copy. Everything that changes during the operation belongs to the new object being built. Because the new object gets its own array from new, the copy and the original never share storage.

Why Copy Assignment Must Clean Up First

The target of copy assignment already exists and already owns an array of elements. That is the central difference from the copy constructor. The copy constructor creates a brand new object, so there is no old state to release. Copy assignment must release the state the existing object already owns, which means it must delete the old elements before it stores the new ones. The source describes two key facts: copy assignment allocates new memory for the copy, and it deletes old elements before assigning new ones.

cpp
Notes on the code
  • p is allocated and filled before the old array is touched
  • The loop reads a.elem and writes only into p
  • delete[] elem releases the old array the target object owned
  • elem = p points the target at the new array
  • sz = a.sz updates the size, and return *this returns the target

The order of operations is the point to read closely. The function first allocates a new array p and copies every element of a into it. Only after that copy is complete does it call delete[] on the old elem array. Then it points elem at p, updates sz, and returns *this. Because the new data is fully built before the old data is released, the function never has to throw away its only copy of the values it still needs.

Tracing x = y One Line at a Time

Worked example

Following x = y Through the Copy Assignment Operator

x holds the values {1.0, 2.0, 3.0} with sz 3. y holds the values {7.0, 8.0} with sz 2. The program executes x = y. What does x hold afterward, and what happens to y?

  1. 1
    Starting state

    x owns a three-value array and sz is 3. Inside the function, a refers to y, so a.sz is 2 and a.elem refers to y's array. These example values are generated for practice.

  2. 2
    Allocate p

    The line double* p = new double[a.sz] creates a new array with two slots. x's old array has not been touched yet.

  3. 3
    Copy the elements

    The loop reads a.elem[0] and a.elem[1], which are 7.0 and 8.0, and writes them into p. Only p is written here. y is only read.

  4. 4
    Delete the old array

    The line delete[] elem releases x's three-value array. This is the only point where x's old values stop existing.

  5. 5
    Reassign the pointer and size

    elem = p points x at the new two-value array, and sz = a.sz sets x's size to 2.

  6. 6
    Return

    return *this hands back x. Now x holds 7.0 and 8.0, and y still holds 7.0 and 8.0 in its own separate array.

Result

x holds {7.0, 8.0} with sz 2. y is unchanged, still holding {7.0, 8.0} with sz 2, in its own array.

The same tracing approach applies to the copy constructor. Walk through the initializer list first, then the loop body, and at each step ask which object is being read and which is being written. In the copy constructor, a is only read, and every write goes into the new object. In copy assignment, a is still only read, but writes go into two places: the new array p, and then x's own members elem and sz.

Where the Result Diverges From Expectation

  • Common mistake

    Assuming the copy constructor and copy assignment do the same job

    Why it fails

    Both copy elements and allocate memory, but the copy constructor builds a brand new object with no old state, while copy assignment acts on an object that already owns elements.

    Do this instead

    When reading the two functions, first ask what already exists when each one runs. Copy assignment must delete the old elements; the copy constructor does not.

  • Common mistake

    Expecting the copy to modify the original

    What it looks like

    Believing that after x = y, the value of y changes to match x's old values.

    Why it fails

    The parameter is a const reference, and the copy assignment only reads a. Writes go into x's members and the new array p.

    Do this instead

    Trace each line and mark the object being read versus written. After x = y, only x changes.

  • Common mistake

    Deleting the old array before copying

    Why it fails

    If the old elements are released before the new array is filled, the values that still need to be copied may no longer be available.

    Do this instead

    Follow the order the source describes: allocate and copy into p first, then delete[] elem, then point elem at p.

  • Common mistake

    Thinking that reassigning elem alone is enough

    Why it fails

    Setting elem = p without updating sz would leave the object with a pointer to a new array but a size that does not match it.

    Do this instead

    The function also sets sz = a.sz so that the pointer and the size describe the same data.

Practice Tracing a Copy

MEDIUM

Vector x holds the values {4.0, 5.0, 6.0, 9.0} with sz 4. Vector y holds {1.0} with sz 1. Trace x = y using the copy assignment operator. At each line, state which object is read and which is written. What does x hold afterward? Name the line where x's original values stop existing, and say what y holds afterward.

Hints
  • Start by writing down the state of x and y before the first line of the function runs.
  • Inside the function, a refers to y, so a.sz is 1 and a.elem refers to y's single-value array.
  • The line that releases x's old array is delete[] elem, and it comes after the copy loop.
  • After elem = p and sz = a.sz run, x should match y and y should be unchanged.

Key Takeaways

  • The copy constructor builds a new object by allocating new storage with elem{new double[a.sz]}, setting sz from a, and copying each element from the original.
  • Copy assignment must release the state the existing target already owns, so it allocates and fills a new array first, then deletes the old elements with delete[] elem, then points elem at the new array and updates sz.
  • The order matters: the new data is fully built before the old data is released, so the copy never loses the values it still needs.
  • Tracing each line and asking which object is read and which is written is the most reliable way to find where a copy result diverges from expectation. In x = y, only x is written, and the line delete[] elem is where x's old values stop existing.

Related concepts

More concepts starting with C