Concepts / User-Defined Types and Their Members

User-Defined Types and Their Members

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

  • Programming

Copying Without Special Instructions

When you create a new object from an existing one in C++, you often do not have to write any special instructions telling the language how to do it. 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. The real question is what, precisely, that default copy does. This article answers that question by following the copy one piece at a time.

Before reading on, consider a small case. Suppose you have an object with two pieces of data, and you create a second object from it. Ask yourself: after the copy, do the two objects hold the same data, and does changing one of them change the other? Keep your answers in mind, because the rest of the article tests them.

Objects as Containers of Members

Definition

A member is one of the named pieces of data inside an object. An object can be pictured as a small container with labeled compartments. Each compartment holds one value.

The compartment picture is what makes the default copy easy to follow. The object is the whole container, and its members are the individual compartments. When an object is copied, the copy operation works through the compartments in turn, taking the value in each one and placing it into the matching compartment of the new object. Nothing about this requires the object to be large or unusual. An object with only two members is copied by handling each of those two members.

The default meaning of copy is called memberwise copy. The name reflects its organization: the copy is organized around members, and it is applied to each of them. Each member of the source object is copied into the corresponding member of the target object. The target object ends up with its own members, each filled from the matching member of the source.

Tracing a Memberwise Copy

The diagram below shows the relationship in words. Each member of the source object is matched with a member of the target object, and each target member is filled from its partner. The result is two separate objects that hold equal member values.

a.x3b.x3a.y7b.y7copied intocopied into
After the copy, which value does each member of the target object hold, and where did it come from?

Now look at a complete program. It uses a small user-defined type named Point with two members. Read the code first and predict what each printed line will show before you look at the output.

cpp
Output
3 7
3 7
3 10
Notes on the code
  • Point is a user-defined type with two members, x and y.
  • Point b = a is an initialization: b is created and receives a's member values.
  • c = a is an assignment: c already exists, and its members are overwritten with a's member values.
  • b.x = 10 changes only the target object b, not the source a.

What do you think happens?

In the example above, what does the last printed line show?

  • 3 10

  • 10 10

  • 3 3

Reveal answer
Answer:

3 10

The copy gave b the member values of a. Changing b.x to 10 changed only b, so a.x still holds 3 while b.x now holds 10.

Initialization and Assignment Compared

Two operations in C++ use this default. The first is initialization, where a new object is created and given the values of an existing object at the moment it comes into existence. The second is assignment, where an object that already exists is overwritten with the values of another object. The source is explicit that both of these operations perform a memberwise copy.

New objectcreated by the copyMembers copiedfrom the sourceExisting objectalready in placeMembers overwrittenfrom the source
How are the two operations alike in the copy they perform, and how do they differ in when that copy happens?

The two operations differ in timing and in what already exists. Initialization creates the target at the moment of copying. Assignment works on a target that is already there, so whatever member values it held are replaced. In both cases the transferred values are the same kind of thing: each member of the source is copied into the matching member of the target.

What the Target Holds Afterward

The before and after states of the target c in the example show the effect of assignment member by member. Before the assignment, c holds 5 in both members. After c = a, each member has been overwritten with the value from the matching member of a.

c.x5c.x3c.y5c.y7overwrittenoverwritten
What does the target object look like member by member before the copy and after it?

After the copy, the source and target hold equal member values. The source does not describe any link between the two objects after the copy, so this article does not claim one. In the example, changing b.x left a.x unchanged. What you can rely on is that the target received equal member values at the moment of the copy and that each object has its own members afterward.

Builtin and User-Defined Types Side by Side

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.

AspectUser-defined typesBuiltin types such as int and double
Copied by defaultYesYes
Default meaning of copyMemberwise copyMemberwise copy
Needs special copy instructions from the programmerNot for the default copyNot for the default copy
Used in initialization and assignmentYesYes

Both kinds of type share the same default copy behavior.

Mistakes About Copied Objects

  • Common mistake

    Believing the default copy only works for large or unusual objects

    Why it fails

    Nothing in the memberwise description requires the object to be large. Even an object with two members is copied by handling each member.

    Do this instead

    Think of every object, however small, as being copied member by member.

  • Common mistake

    Assuming that initialization and assignment copy differently

    Why it fails

    The source states that both operations perform a memberwise copy and transfer the same member values.

    Do this instead

    Differ only in timing: initialization creates the target, while assignment overwrites a target that already exists.

  • Common mistake

    Assuming builtin types need a different copy rule than user-defined types

    Why it fails

    The default meaning of copy is the same memberwise copy for both kinds of type.

    Do this instead

    Apply the same member-by-member reasoning to int, double, and your own types.

  • Common mistake

    Expecting a change to the target to change the source

    What it looks like

    After Point b = a, setting b.x to 10 leaves a.x at 3.

    Why it fails

    After the copy each object has its own members, and the source does not describe any link between them.

    Do this instead

    Treat the target as its own object once the copy is made.

  • Common mistake

    Expecting the target to keep its old member values after assignment

    What it looks like

    Before c = a, c held 5 and 5. After the assignment it holds 3 and 7.

    Why it fails

    Assignment overwrites the existing members with the source's member values.

    Do this instead

    After an assignment, read the target's members as the source's values.

Practice: Tracing Your Own Copy

EASY

Suppose a user-defined type has two members, p and q. An object d has p equal to 4 and q equal to 9. You then write Point e = d; followed by e.p = 0;. Without running anything, state the values of d.p, d.q, e.p, and e.q after these two statements. Then explain why d.p did not change.

Hints
  • Start by asking which member values the initialization transfers to e.
  • Remember that the change to e.p happens after the copy, on a separate object.
  • Check whether the source object is affected by an edit to the target.

Key Takeaways

  • Objects can be copied by default, and this holds for user-defined types and builtin types alike.
  • The default copy is a memberwise copy: each member of the source is copied into the corresponding member of the target.
  • Both initialization and assignment perform a memberwise copy; initialization creates the target, while assignment overwrites an existing one.
  • After a default copy the source and target hold equal member values, and each object has its own members afterward.

Related concepts

More concepts starting with U