Concepts / Default Copy Behavior

Default Copy Behavior

Objects can be copied by default, and this holds for user-defined types and builtin types alike.

  • Programming

Copies You Get Without Asking

When you create a new object from an existing one in C++, you usually do not have to write any special instructions telling the language how to do the copying. Objects can be copied by default. This holds for objects of types you define yourself, and it also holds for builtin types such as int and double.

This article answers a precise question: what, exactly, does that default copy do? The short answer is that the default copy works member by member. Each piece of data that makes up an object is transferred from the source to the new object, one piece at a time. Once you see that pattern, the behavior of many everyday C++ statements becomes predictable.

Objects as Labeled Compartments

A member is one of the named pieces of data inside an object. A useful way to picture an object is as a small container with labeled compartments. Each compartment holds one value. The copy operation works through the compartments in turn, moving the value in each one into the matching compartment of the new object.

This is the idea behind the name memberwise copy. The copy is organized around members and is applied to each of them. Nothing about this requires the object to be large or unusual. Even an object with only two members is copied by handling each of those two members.

source.first3target.first3source.second7target.second7copiedcopied
Where does each member's value end up once the default copy has transferred every member from the source object to the target object?

Read the diagram from left to right. The source object on the left still holds its own members. The target object on the right has its own members, and each one is filled from the matching member of the source. The result is two separate objects that hold equal member values. The source does not describe any further link between the two objects after the copy, so you should not assume one.

Tracing a Pair Through Initialization and Assignment

The following example is an original illustration rather than the source's own code. It uses a small user-defined type called Pair with two int members, so you can follow each member through the copy. Before reading the output, try to predict what each printed line will show.

What do you think happens?

Pair a is set to {3, 7}. Pair b is initialized from a, and Pair c is then assigned from a. What does each of the three printed lines show?

  • Only b matches a; c still holds its original 0 and 0

  • All three lines show 3 and 7

  • The lines show 0 and 0 because copies start empty

Reveal answer
Answer:

All three lines show 3 and 7

Initialization copies a's members into b as b is created, and assignment copies a's members into the existing c, overwriting c's 0 and 0. Each step performs a memberwise copy, so all three objects end up with the same member values.

cpp
Output
3 7
3 7
3 7
Notes on the code
  • Pair is a user-defined type with two members, first and second
  • Pair b = a copies each member of a into the new object b during initialization
  • c = a copies each member of a into the existing object c during assignment
  • Each printed line shows the two member values of one object

Notice that the source in this example is never changed by either copy. The line Pair b = a leaves a with 3 and 7, and the line c = a leaves a with 3 and 7 as well. The copy moves values out of the source and into the target, and the source keeps its own member values.

Initialization and Assignment Compared

C++ uses two operations that make use of the default copy. The first is initialization, in which a new object is created and given the values of an existing object at the moment it comes into existence. In the example above, b is created this way. The second is assignment, in which an object that already exists is overwritten with the values of another object. The c in the example is updated this way, even though it had values before the assignment.

The two operations differ in when the target comes into existence, but they share the same core action. The source is explicit that both perform a memberwise copy, so they transfer the same member values. This is why the three variables in the example end up holding the same value even though only one of them was set up by initialization.

Pair b = acreates a new objectc = aoverwrites an existingobjectmemberwise copymembers taken from amemberwise copymembers taken from aperformsperforms
How do the steps differ when a new object is created from an existing one versus when an existing object is overwritten, and what do they have in common? Both columns end in the same memberwise copy.

Does the Type Matter?

A natural question is whether the default copy behaves differently for types you write yourself and for types the language provides. The source says it does not. Objects can be copied by default for user-defined types, and this is also true for builtin types. The default meaning of copy is the same memberwise copy in both cases.

Pairuser-defined typeintbuiltin typedefault copymemberwise copydefault copymemberwise copyusesuses
Does copying a user-defined object behave differently from copying a builtin value, and how do the two compare side by side? The source says the default meaning of copy is the same in both columns.
cpp
Output
5 9
Notes on the code
  • int y = x copies the value of the builtin int x into the new object y
  • y = 9 overwrites only y; x keeps its own value

Mistakes Learners Make

  • Common mistake

    Thinking a copy happens only for simple builtin values

    What it looks like

    Assuming Pair b = a needs special code because Pair is a type you wrote yourself

    Why it fails

    The source says objects can be copied by default for user-defined types as well as builtin types

    Do this instead

    Expect the default memberwise copy for your own types unless you have been told otherwise

  • Common mistake

    Believing initialization and assignment copy different things

    What it looks like

    Thinking b = a copies everything while c = a copies only one member

    Why it fails

    The source states that both initialization and assignment perform a memberwise copy and transfer the same member values

    Do this instead

    Treat both as a member-by-member transfer and check each member when you trace the result

  • Common mistake

    Assuming that copied objects stay linked after the copy

    What it looks like

    Expecting a change to b to alter a after the copy

    Why it fails

    The source describes two separate objects holding equal member values and does not describe any link between them after the copy

    Do this instead

    Trace each object separately, using the member values each one holds

  • Common mistake

    Forgetting that the source object is not changed by a copy into a new object

Practice: Trace the Members

MEDIUM

Using the Pair example, suppose you add the line b.first = 10; after Pair b = a; but before the assignment to c. Trace each of the three objects and state the values of first and second in a, b, and c at the point where c = a runs. Then say what the final output looks like for the first line.

Hints
  • Start by writing down the member values of a, which are never changed in this change
  • Remember that b.first = 10 changes one compartment of b only
  • Assignment c = a copies the members of a into c, overwriting whatever c held

Key Takeaways on Default Copying

  1. 1
    Objects can be copied by default, for user-defined types and for builtin types alike
  2. 2
    The default copy is memberwise: each member of the source is copied into the corresponding member of the target
  3. 3
    Initialization and assignment both perform a memberwise copy and transfer the same member values
  4. 4
    After a default copy the source and target hold equal member values, and the source does not describe any link between them

At a glance

  • Objects can be copied by default, and this holds for user-defined types and builtin types alike
  • The default copy is memberwise: each member of the source is transferred into the matching member of the target
  • Initialization and assignment both perform a memberwise copy, so they transfer the same member values
  • After a default copy, the source and target hold equal member values, and the source does not describe any link between them

Related concepts

More concepts starting with D