Concepts / Copy Assignment

Copy Assignment

A copy constructor creates a new object from an existing one, and for the Vector class it allocates space for the required number of elements and then copies the elements into that space.

  • Programming

Two Names, One Set of Elements?

Suppose you have a Vector called v1 that holds several elements, and you write Vector v2 = v1;. What should v2 contain afterward? The answer depends on how the class copies itself. A simple class with plain fields may only need its fields copied one by one. A class that owns a dynamically allocated array is different: the new object needs its own array, and the values must be copied into that array. This article follows the Vector class through that situation, first for creating a new object and then for assigning to an object that already exists.

Before reading any implementation, form a prediction. What do you expect v2 to share with v1: the same array, or a separate array holding the same values? Keep that guess in mind as you trace the code.

When the Copy Constructor Runs

Definition

A copy constructor creates a new object from an existing one. For the Vector class, it allocates space for the required number of elements and then copies the elements into that space.

The copy constructor is the code that runs when a new Vector is created from an existing Vector. In the declaration Vector v2 = v1;, the new object v2 is constructed as a copy of v1. The source frames this with the example v2=v1: the constructor builds a new object, and it does not change an object that already exists.

The constructor takes a const reference to the source object, which is named a in the source's form. A const reference lets the constructor read the source without changing it. The member initializer list allocates the array and records the size. The body then copies the elements one at a time. Allocation happens first, so the body has a place to put each value.

Notes on the code
  • const Vector& a: the source object, read but not modified
  • sz(a.sz): the new object records the same size
  • elem(new double[a.sz]): the new object gets its own storage
  • the loop copies each element from the source's storage into the new storage

Deep Copy Gives Each Object Its Own Elements

The important property of this constructor is that the new object does not borrow the storage of the original. Allocating new memory and copying the values into it is what makes the copy a deep copy. The new object has its own copy of the original object's data. Changing the elements of one object does not change the elements of the other.

v1size 3v1 buffer1, 2, 3v2size 3v2 buffer1, 2, 3elemelem
After Vector v2 = v1 runs, where do each object's elements live, and what happens if v2's buffer changes?

The values 1, 2, 3 in the diagram are illustrative and are not taken from the source. The key point is that each Vector object points to its own array of elements. Each object owns its buffer, so writing into one buffer leaves the other buffer as it was.

Tracing Vector v2 = v1

Worked example

Tracing the copy constructor on a three-element Vector

v1 holds the elements 1, 2, 3 and has size 3. The declaration Vector v2 = v1; runs. What does v2 hold, and does it share storage with v1?

  1. 1
    Identify the source

    The parameter a refers to v1. The constructor reads a.sz and a.elem but does not change them.

  2. 2
    Initialize the members

    The member initializer list sets sz to a.sz, which is 3. It also runs new double[a.sz], so the new object gets a fresh array of three slots.

  3. 3
    Copy the elements

    The loop runs for i from 0 to 2. Each step reads a.elem[i] from v1's array and writes it into elem[i] in v2's new array.

  4. 4
    Check the result

    v2 has size 3 and holds 1, 2, 3. Its elem pointer addresses a different array from v1's elem pointer.

Result

v2 holds the same values as v1 in its own separate array, so v2 is a deep copy and changing one does not change the other.

Mistakes in Copy Constructor Implementations

  • Common mistake

    Copying without allocating

    What it looks like

    The constructor sets sz from a.sz and then runs elem[i] = a.elem[i] with no new array.

    Why it fails

    elem has no storage to write into, so the copy writes to memory the object does not own.

    Do this instead

    Allocate with new double[a.sz] in the initializer list before the loop writes any element.

  • Common mistake

    Copying only the pointer

    What it looks like

    The constructor writes elem(a.elem) and skips the loop.

    Why it fails

    Both objects now share one array, which is a shallow copy and not a deep copy.

    Do this instead

    Allocate a new array and copy each value into it, so each object owns its elements.

  • Common mistake

    Inconsistent sizes in the allocation and loop

    What it looks like

    The array is allocated with new double[a.sz] but the loop runs to a different bound, such as a fixed count.

    Why it fails

    If the allocation and the loop disagree, some elements are never copied, or the loop writes past the end of the array.

    Do this instead

    Use the same size value for both the allocation and the loop bound, such as sz.

Why Assignment Needs Its Own Operator

A copy constructor handles the case where the new object does not exist yet. Copy assignment handles a different case: assigning one existing object to another existing object of the same class. The source describes copy assignment as copying contents into an object that already exists, and it says this operation is needed in addition to a copy constructor.

Vector v2 = v1;v2 is newCopy constructorallocates and copiesv2 = v1;v2 already existsCopy assignmentreplaces v2 contentsrunsruns
Vector v2 = v1 creates a new object, so the copy constructor runs. v2 = v1 changes an object that already exists, so copy assignment runs.

The two operations do similar copying work but at different moments. The constructor runs as part of creating an object. Assignment runs later, when the destination already holds its own elements. That destination must release those old elements, which is why assignment does more than the constructor does.

The this Pointer and the Return Value

Inside a member function, the name this refers to the object on which the function was called. The source states that the destination is the object for which operator= is called, and this points to that object. The source is the object passed in as the parameter a. Inside operator=, the destination is therefore reached through this, and the source is reached through a.

The operator returns a reference to the destination. The source describes this as returning the current object by reference, through the expression that dereferences this. The return type is a reference to the destination, not the source. Checking the return expression is one of the steps the source recommends when tracing an unexpected result.

Copy Assignment Step by Step

The operator must allocate storage for the source elements, copy them, and make the destination use that storage. A copy of the count alone is not enough, because the destination's old elements must be replaced by copies of the source's elements.

Notes on the code
  • Vector& operator=(const Vector& a): the parameter a is the source; the destination is the object the operator is called on
  • new double[a.sz] and the loop: new storage is allocated and filled before anything is deleted
  • delete[] elem: the destination's old elements are released only after the copy loop
  • elem = p and sz = a.sz: the destination now uses the new buffer and size
  • return *this: returns a reference to the destination
Before
dst.elem -> [7, 8]  dst.sz = 2

dst holds two elements, 7 and 8, in its own buffer

After
dst.elem -> [1, 2, 3]  dst.sz = 3

dst holds copies of 1, 2, 3 from src, and its old buffer has been released

dst buffer7, 8dst buffer1, 2, 3dst size2dst size3
What does the destination Vector hold before and after dst = src runs, and which old resources are released?

In the diagram, the old buffer holding 7 and 8 is the resource released by the delete[] step. The source's buffer is only read, so it is unchanged. The values 7, 8, 1, 2, 3 are illustrative and are not taken from the source.

Self-Assignment and the Order of Operations

The source and destination may be the same object, as in v = v;. In that case, the parameter a and this refer to the same Vector. The order of steps decides whether the data survives. Consider the order the source describes: copy first, then delete the destination's old elements.

Begin operator=Allocate new bufferCopy source elementsDelete old bufferPoint elem at newbufferSet sizeReturn this
In v = v, what sequence of steps keeps the data available until it has been copied?

Follow the flowchart with v = v. The new buffer is allocated and filled with copies of v's current elements before the old buffer is deleted. When the old buffer is deleted, the copies already exist in the new buffer, so no data is lost. If the old buffer were deleted first, the loop would read from memory that had already been released, and the data would be destroyed before it was copied.

Predict the Copy Before Checking

What do you think happens?

After Vector v2 = v1; runs, and then v2[0] is set to 99, what does v1[0] hold?

  • 1

  • 99

Reveal answer
Answer:

1

The copy constructor allocated a separate array for v2 and copied the values into it. Writing 99 into v2's array does not touch v1's array, so v1[0] still holds 1.

MEDIUM

A class stores a dynamically allocated array of size sz. Its copy assignment operator runs delete[] on the old array before it allocates and copies the new elements. Trace what happens when the operator is called as v = v. Which element values are still available when the copy loop runs, and what change would fix the order?

Hints
  • Ask whether the source's elements can still be read after the old buffer is deleted.
  • Compare the order against the sequence: allocate, copy, delete, install, return this.

Key Takeaways

  • A copy constructor creates a new object from an existing one; for Vector it allocates space for the elements and copies them in, and it runs when a new object is created, as in Vector v2 = v1;.
  • A deep copy gives each object its own element buffer, so changing one object's elements does not change the other's.
  • Copy assignment replaces the contents of an existing destination object with copies from a source object, and it is needed in addition to a copy constructor.
  • Inside operator=, this identifies the destination, the parameter a identifies the source, and the operator returns a reference to the destination.
  • Allocate and copy before deleting the old elements, so a self-assignment such as v = v does not destroy the data.

Related concepts

More concepts starting with C