Pointers and Object Ownership
Default memberwise copy duplicates each member of a class, which is correct for plain values but problematic for pointers.
A Copy That Is Not a Copy
Copying an object usually feels like the simplest operation in a program. You write one line that gives a new name to a value, and you expect two independent things to exist afterward. For many ordinary types that expectation is correct. For one important family of classes it is not, and the failure is quiet: the program compiles, runs, and produces wrong results only when one of the two objects is changed later. This article is about that gap between what a copy looks like and what it actually does.
The scenario comes from a Vector class whose objects own a collection of elements. Assigning one Vector to another is written as a single line, and the natural instinct is to assume the second Vector receives its own elements. The rest of this article tests that instinct, because the language's default behavior does not guarantee it.
What do you think happens?
A function receives a Vector named v1 that has four elements. Inside it, a second Vector named v2 is initialized from v1 by the default copy. Then the element at index 0 of v1 is set to 2. What does the element at index 0 of v2 hold afterward?
Reveal answer
2, because v2 and v1 lead to the same set of elements
Each Vector object holds a pointer to its elements. Default copy produces a second object whose pointer has the same value as the first, so both objects lead to one set of four elements. Writing to index 0 through v1 changes that shared set, and reading index 0 through v2 reads the same set.
What Default Memberwise Copy Does
Default memberwise copy works by copying each member of an object one at a time. Nothing about this process is special: the copy operation walks through the object's members and duplicates each one into the new object. The rule is simple, and for a class whose members hold plain values it is exactly what you want. If an object holds a plain integer, the new object gets its own integer with the same value, and changing one does not affect the other.
The trouble starts when a member is not the data itself but a pointer that leads to data stored elsewhere. Copying the pointer copies the route to the data, not the data. After the copy, the two objects have two pointer members with identical values, and those values point at the same storage. The copy looks complete from the outside, because every member has been duplicated, yet the thing the members refer to has not been duplicated at all.
Compare two members. A plain int member is copied by value: the new object receives a value, and that value is stored in a separate place. A pointer member is copied as an address: the new object receives the same address, and both objects are now routes to one target. The same copy operation produces independent results for one member and shared results for the other.
Tracing the Copy Step by Step
The clearest way to see the shared resource is to follow the copy one step at a time. The trace below uses the Vector scenario from the opening. Each step names what changes, so you can check exactly where the two objects begin sharing one set of elements.
Tracing the Vector copy and the two writes
v1 holds four elements. v2 is initialized from v1 by default copy. Then index 0 is written through v1, and index 1 is written through v2. What does each object see at indexes 0 and 1?
- 1Step 1: Start with one object and one set of elements
v1 has a pointer member that leads to four elements. Only v1 is involved so far.
- 2Step 2: The default copy duplicates the pointer, not the elements
v2 receives its own pointer member, but the value stored in that member is the same value v1 holds. Both pointers now lead to the same four elements. This is the exact step where the two objects begin sharing one resource.
- 3Step 3: Write index 0 through v1
The write goes through v1's pointer into the shared set. The element at index 0 of the shared set changes, and that change is visible to any object that leads to the same set.
- 4Step 4: Write index 1 through v2
The write goes through v2's pointer into the same shared set. Now v1 sees the new value at index 1, even though the write was performed through v2.
- 5Step 5: Read both objects
Reading index 0 through v2 returns the value written through v1. Reading index 1 through v1 returns the value written through v2. Neither object owns a private copy of the elements.
Both objects see the same elements at every index. The writes through one object are visible through the other, which is the surprise the opening prediction tested.
Why the Handle's Invariant Breaks
A handle carries an invariant, which is a property it is supposed to keep true at all times. For a resource handle, the invariant concerns ownership of the resource. A healthy handle has a clear answer to the question of who owns the resource it leads to. Default memberwise copy can break that invariant, because after the copy the program has two handles and only one arrangement of storage, with neither handle clearly owning it alone.
The transition from one owner to two sharers happens at the copy, not later. The program can still run normally for a while after the copy, which is why the failure is quiet. The damage becomes visible only when one of the objects changes the shared resource, or when the objects clean up that resource. Until then, nothing in the code looks wrong.
Reading a Destructor as a Warning
The Vector class has a destructor, and this is a useful clue. A destructor is the place where a class cleans up the resource it owns when an object goes away. If a class needs to release something at the end of its life, it is almost always because it owns that something. Ownership is exactly what default copying muddles, since two objects end up pointing at one resource. So when a class has a destructor, a programmer should treat the default copy behavior with suspicion.
The source makes the same point from the compiler's side. The presence of a destructor is a strong hint that default memberwise copy semantics is wrong, and the compiler should at least warn about an example like this one. The practical lesson is that a class of this kind needs its copy behavior defined on purpose, rather than accepting whatever the default produces.
Mistakes Beginners Make With Copying
Common mistake Assuming every copy produces independent data
What it looks likeCopying a Vector with one assignment line and expecting writes to the copy to leave the original unchanged.
Why it failsThe default copy duplicates each member. For a pointer member, the duplicated value is the route to the same data, so both objects share the elements.
Do this insteadCheck whether the class holds pointers to resources. If it does, the copy behavior must be defined deliberately instead of trusted to the default.
Common mistake Treating a pointer copy as if it copied the data it points to
Why it failsCopying the pointer copies the route to the data, not the data. Two routes can lead to one target.
Do this insteadAsk what the pointer leads to and who is responsible for it. Only then decide whether each object should receive its own copy of that data.
Common mistake Ignoring a destructor because the program runs without errors
What it looks likeA class releases a resource in its destructor, but its copy behavior is never reviewed.
Why it failsThe destructor is a sign that the class owns a resource. Default copying of such a class can leave two objects sharing that resource, and the failure may only appear at cleanup.
Do this insteadTreat a destructor as a prompt to review the copy behavior of the class.
Practice: Spot the Shared Resource
A class stores a pointer member that leads to a block of data, and its destructor releases that block. An object a is created, and then object b is initialized from a using the default copy. Without any custom copy behavior, describe which member of b holds the same value as the matching member of a, what both objects lead to, and at which step the two objects begin sharing one resource.
Hints
- Ask which member is a plain value and which member is a pointer.
- Follow the copy one member at a time: the pointer is duplicated as a value, not as data.
- The destructor tells you the class owns the resource, so consider what happens to it when both objects go out of scope.
Why Custom Copy Behavior Is Needed
When a class owns a resource through a pointer, define its copy behavior on purpose. The source's next step is to define better copy semantics for resource handles, rather than accepting whatever the default produces. The goal is that each handle has a clear answer to the question of who owns the resource, so that the invariant holds after a copy as well as before it.
A destructor, a copy constructor, and a copy assignment operator are related special member functions. A destructor that releases a resource is a signal that the copy operations must also be considered, because a default copy will not give each object its own resource.
Key Takeaways
- Default memberwise copy duplicates each member, which is correct for plain values but leaves pointer members sharing one target.
- After a default copy of a pointer member, two objects lead to one resource, and writes through either object are visible through the other.
- A resource handle's invariant concerns ownership; default copying can leave two handles with no clear owner of one storage arrangement.
- A destructor is a strong signal that a class owns a resource and that its default copy semantics should be reviewed.
- Resource-owning classes need copy behavior defined deliberately, rather than left to the default.