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.
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?
Reveal 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.
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
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.- 1The 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.
- 2The 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.
- 3sz{a.sz} records the same size in the new object, so the copy knows how many slots to fill.
- 4The 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.
- 5When 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.
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.
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
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?
- 1Before the first line runs
x owns the array of three values and sz is 3. y holds two elements.
- 2Inside the function
The parameter a is the same object as y, so a.sz is 2 and a.elem refers to y's array.
- 3Allocate p
The new array p is created with two slots. Nothing has been released yet, so x still holds its three values.
- 4Copy the elements
The loop copies y's two elements into p. Only the source array is read here.
- 5Release the old array
The delete[] line releases x's old three-value array. This happens only after the copy is complete.
- 6Reassign
elem is set to p, sz becomes 2, and *this, which is x, is returned.
x now holds the two elements from y and sz is 2. y is unchanged, still holding its own two elements.
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 failsThe parameter is a const reference, so the constructor only reads from the original. The writes go into the new object.
Do this insteadAsk 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 failsThe 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 insteadCheck 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 likex holds three values and y holds two. After x = y, x.sz is 2, not 3.
Why it failsThe function reassigns elem and sz from the new array p, so the old three-value array is released.
Do this insteadTrace 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 failsIn self-assignment, the source and target share the same array. Releasing it first would leave the copy loop reading freed memory.
Do this insteadKeep the order allocate, copy, delete, reassign.
Practice: Predicting a Copy
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.