Concepts / Copy Constructor Basics

Copy Constructor Basics

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

Two Vectors That Must Not Share Storage

A Vector object keeps its elements in a dynamically allocated array. Its member elem points to that array, and its member sz records how many elements the array holds. When one Vector is copied into another, the two objects need to be handled with care, because the storage is not simply a number that can be duplicated. This article starts from working code. You will read two member functions, a copy constructor and a copy assignment operator, and then trace what each one does to the state of the objects involved.

What do you think happens?

Suppose Vector x holds three elements and Vector y holds two different elements. After the program executes x = y, how many elements does x hold?

  • Three, because x keeps its old array

  • Two, because x takes on the contents of y

  • Five, because the two arrays are combined

Reveal answer
Answer:

Two, because x takes on the contents of y

Copy assignment builds a new array from y's elements, releases x's old three-value array, and then points x at the new array with sz set to 2. Tracing the function line by line shows exactly how that happens.

Tracing What the Copy Constructor Builds

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. The result is two objects, each with its own elem pointer. Each pointer leads to its own array, so the original and the copy do not share elements.

original Vectorsz = 3original array3 doublesnew Vectorsz = 3new array3 doubleselemelem
After the copy constructor runs, where does each Vector's elem pointer point, and why does changing one Vector's elements leave the other unchanged?

Read the diagram as two separate storage structures. The new Vector's elem pointer leads to a freshly allocated array, not to the original's array. Because the storage is separate, writing to an element through one object does not change the element seen through the other. This independence is the whole purpose of the copy constructor.

Reading the Copy Constructor Line by Line

python
Code walkthrough
The copy constructor takes a const reference to the original Vector and builds a new object from it. The excerpt above is an illustrative reconstruction of the member functions described in the source; other members are omitted.
  1. 1
    The parameter is const Vector& a. It is a reference to the original object, and const means the constructor is not allowed to change it. Every write in this function goes into the new object being built.
  2. 2
    The member initializer list elem{new double[a.sz]} requests an array with as many doubles as the original holds. This is the allocation step, and it gives the new object its own storage.
  3. 3
    sz{a.sz} records the same size in the new object, so the copy knows how many slots to fill.
  4. 4
    The loop runs i from zero up to sz. Each pass reads a.elem[i] from the original and writes it into elem[i] of the new object at the same index.
  5. 5
    When the loop finishes, the new object holds the same values as the original, but in an array that belongs only to it.

Copy Assignment Must Release Old Storage

The copy constructor and the copy assignment operator both copy elements and both allocate new memory, so they look alike on the page. The key difference is what already exists when they run. The copy constructor creates a brand new object, so there is no old state to clean up. Copy assignment acts on an object that already owns an array of elements, so it must release those old elements before it takes on the new ones.

python

The order of operations is the important part. 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. The diagram below shows this sequence as a series of steps between the objects and arrays involved.

x (this)the object beingoverwritteny (a)the source objectnew array pallocated firstold elem arrayreleased after the copy1. allocate p with a.sz slots2. copy each a.elem[i] into p[i]3. delete[] old elem4. elem = p and sz = a.sz
In what order does operator= allocate, copy, delete, and reassign, and what would go wrong if the delete happened before the copy?

Why does the copy have to come before the delete? The source reads elements from a, and when a is a different object, its array stays intact no matter what happens to x. The ordering matters most when the two objects are the same one, as in self-assignment. In that case a.elem is the same array as elem. If delete[] ran first, the loop would then read from memory that has already been released. Copying into the new array before releasing the old one keeps the source readable for the whole operation.

Tracing x = y Through the Function

Worked example

Tracing x = y when x holds three elements and y holds two

Vector x already holds three elements, sz is 3, and x owns a three-value array. Vector y holds two different elements. The program executes x = y. What are x and y after the statement finishes?

  1. 1
    Before the first line runs

    x owns the array of three values and sz is 3. y holds two elements.

  2. 2
    Inside the function

    The parameter a is the same object as y, so a.sz is 2 and a.elem refers to y's array.

  3. 3
    Allocate p

    The new array p is created with two slots. Nothing has been released yet, so x still holds its three values.

  4. 4
    Copy the elements

    The loop copies y's two elements into p. Only the source array is read here.

  5. 5
    Release the old array

    The delete[] line releases x's old three-value array. This happens only after the copy is complete.

  6. 6
    Reassign

    elem is set to p, sz becomes 2, and *this, which is x, is returned.

Result

x now holds the two elements from y and sz is 2. y is unchanged, still holding its own two elements.

x.sz3x.sz2x elements3 valuesx elements2 values from yy.sz2y.sz2y elements2 valuesy elements2 values
What are the values of sz and the elements of both Vector objects before and after the copy, and at which line does the result stop matching what the learner expected?

Compare the two states. Only x changes. Its sz goes from 3 to 2, and its elements now come from y. y's sz and elements are the same before and after. A common expectation is that x keeps its three old values, or that the final result is a mix of both arrays. Neither is what the code does. The line where a learner's expectation most often breaks is the reassignment of elem, because after that line the old array is gone from x and cannot be recovered.

Mistakes When Reading Copy Code

  • Common mistake

    Assuming the copy constructor changes the original object

    Why it fails

    The parameter is a const reference, so the constructor only reads from the original. The writes go into the new object.

    Do this instead

    Ask which object is read and which is written on each line, as the trace method suggests.

  • Common mistake

    Treating copy assignment like the copy constructor

    Why it fails

    The copy constructor works on a brand new object. Copy assignment works on an object that already owns elements, so its old array must be released.

    Do this instead

    Check whether the target object already holds state, and if it does, expect a delete[] step.

  • Common mistake

    Expecting x to keep its old elements after x = y

    What it looks like

    x holds three values and y holds two. After x = y, x.sz is 2, not 3.

    Why it fails

    The function reassigns elem and sz from the new array p, so the old three-value array is released.

    Do this instead

    Trace the reassignment lines, which are the point where the result diverges from the expectation.

  • Common mistake

    Thinking the delete can safely happen before the copy

    Why it fails

    In self-assignment, the source and target share the same array. Releasing it first would leave the copy loop reading freed memory.

    Do this instead

    Keep the order allocate, copy, delete, reassign.

Practice: Predicting a Copy

MEDIUM

Vector a holds four elements, and Vector b holds one element. The program executes b = a. Before running any trace, predict the value of b.sz afterward, and say whether a's elements change. Then trace the copy assignment order: allocate, copy, delete, reassign. Identify the exact line where a learner who expects b to keep its one old element would see the result differ.

Hints
  • Which object is the parameter a, and what is a.sz?
  • Which line allocates the new array, and which line releases b's old array?
  • After the reassignment line, can b's old array still be reached?

A correct answer predicts b.sz is 4, a's four elements are unchanged, and the divergence happens at the reassignment of elem and sz. The first line where a learner's picture of b breaks is the one that replaces elem with p.

Key Takeaways

  • The copy constructor allocates new storage with elem{new double[a.sz]}, sets sz from a, and copies each element, so the new object owns its own array.
  • The copy constructor only reads from the original because its parameter is a const reference; all writes go into the new object.
  • Copy assignment 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.
  • Tracing each line and asking which object is read and which is written is the most reliable way to locate where a copy result diverges from expectations.

Related concepts

More concepts starting with C