Concepts / Base and Derived Classes in C++

Base and Derived Classes in C++

Default copy and move operations are problematic in a class hierarchy because the base class does not know about the members of the derived class.

  • Programming

When Default Copying Looks Safe

When you define a class, the language is willing to supply copy and move operations for you. For a simple, self-contained class, letting those defaults do the work is often reasonable. Inside a class hierarchy, however, the same shortcut can quietly go wrong. This article looks at why the default behavior breaks down once a base class and its derived classes are involved, and what the usual remedy is.

The mistaken belief this article targets is that default copy and default move operations work correctly for a class that sits in a hierarchy. The belief is easy to hold, because the defaults work well in ordinary cases. The source describes the situation as typically a disaster, so the belief is worth testing before you rely on it.

What do you think happens?

A derived object has a member of its own, beyond what the base class declares. If a copy is made into a base-class object, what happens to that derived member?

  • It is copied into the base object along with everything else

  • It is dropped, and the base object keeps only the base-class members

  • It is stored in a hidden extra slot inside the base object

Reveal answer
Answer:

It is dropped, and the base object keeps only the base-class members.

The base-class side of the code has no information about members that exist only in the derived class, so the base object has no place to hold them. The sections below show why this follows from what a base-class view of the object can see.

Where the Defaults Lose Track of Derived Members

The key idea is about knowledge. The default copy and move operations are generated for the class in which they appear. A default operation for a base class therefore reflects only the base class's members. It has no way to reach members that exist only in a derived class. This is the core reason the defaults cannot be assumed correct in a hierarchy.

The problem is not that the derived object lacks the extra members. The object really does have them, and a faithful copy would have to account for them. The difficulty is that the base class's default operation was written with only the base class in view. Whatever it copies, it copies from the base class's own list of members.

Think of the base class as a description of what every object in the family has in common. The derived class adds details that only some family members have. A default operation written from the base description can handle the shared details, but it has no vocabulary for the extra details, because the base description never mentions them.

What a Base Pointer Can See

A pointer's declared type tells you what the pointer can see. A pointer to a base class gives you access to the base class's view of the object, and nothing more. The derived class may add members of its own, but code that holds only a base pointer has no information about them. When a copy or move must duplicate the whole object, the operation needs to know every member it has to duplicate. Through only a base pointer, that knowledge is missing.

Base pointerdeclared as base typeDerived objectthe object in memoryBase membersvisible through the pointerDerived memberspresent but hiddenpoints tocontainscontainscan accesscannot access
If a base pointer points to a derived object, which members can be reached through the pointer and which exist in memory but stay hidden?

Read this diagram as a statement about visibility. The object really does have derived members, and a copy must account for them. But the code that holds the base pointer can reach only the base members, so a default operation working from that view has no basis for copying the derived part correctly.

Tracing a Default Copy Through a Base Pointer

Follow one default copy from start to finish. Code holding a base pointer asks for a copy. The default copy operation for the base class runs, and it works only with the members the base class declares. It can copy those members. It has no route to the derived members, so the copy that results cannot be a correct duplicate of the whole object.

Base pointerDefault copygenerated for base classBase membersknown to the operationDerived membersnot known to the operation1. copy requested2. copies base part3. no known route
During a default copy, which operations run in order, and at which step does the base class lose track of the derived members?

Slicing a Derived Object Into a Base Object

The same limitation shows up when a derived object is copied directly into a base-class object. The example below is an original illustration written for this article, not taken from the source. It uses a base class with one member and a derived class that adds a second member.

cpp
Output
b.id is 7. There is no member named extra in b.
Notes on the code
  • Base declares only id, so every Base object has only id.
  • Derived adds extra, which exists only in Derived objects.
  • The copy into b keeps the Base part of d and has no slot for extra.
id (base member)7id (base member)7extra (derivedmember)42extra (derivedmember)no slot in base objectkeptdropped
When a derived object is copied into a base-class object, which parts are kept and which are dropped?

The derived member extra is the part that does not survive. The base object is a smaller object than the derived one, and the base class has no place to keep what it was never told about. This is the same knowledge gap described earlier, now visible in a concrete copy.

Why the Remedy Is to Delete Both Operations

Given the problem above, the source gives a direct recommendation: the best thing to do is usually to delete the default copy and move operations. Deleting them means eliminating the default definitions of those two operations for the class. The hierarchy then no longer offers a default copy or move that would silently do the wrong thing.

Notice that the recommendation names both operations. Copy and move are handled together because both face the same limitation: each needs to know the derived members in order to duplicate or transfer them. Deleting only one of the two would leave the other default in place to fail in the same way.

Mistakes Beginners Make With Hierarchies

  • Common mistake

    Assuming default copy works because it works for a simple class

    What it looks like

    A class with only plain values copies cleanly, so the same habit is applied to a base class that a derived class extends.

    Why it fails

    The defaults work in ordinary cases, but a base class's default operations reflect only the base class's members.

    Do this instead

    Check whether the base class is part of a hierarchy before relying on its default copy and move.

  • Common mistake

    Believing a base pointer gives access to all of a derived object

    What it looks like

    A base pointer is used to copy an object whose derived members hold important state, and the derived state is assumed to be carried along.

    Why it fails

    A base pointer reveals only the base class's view of the object. Derived members exist in memory but stay hidden through that pointer.

    Do this instead

    Treat the declared type of the pointer as the limit of what the code can see and manipulate.

  • Common mistake

    Deleting only the copy operation and keeping the default move

    What it looks like

    The copy constructor is deleted, but the move operations are left at their defaults.

    Why it fails

    Copy and move face the same limitation, because each needs to know the derived members to duplicate or transfer them.

Practice Deciding for a Shape Hierarchy

MEDIUM

A base class Shape declares a name member and nothing else. A derived class Circle extends Shape and adds a radius member that only circles have. Should the default copy and move operations of Shape be kept, or should they be deleted? Explain your reasoning in terms of what the Shape default operations can see.

Hints
  • Ask which members the default operation of Shape knows about.
  • Consider whether radius, which exists only in Circle, can be reached from a Shape-only view.
  • Recall the source's advice to check each base class on its own terms.

Key Takeaways

  • Default copy and move operations are generated for a class and reflect only that class's members, so they can fail in a class hierarchy.
  • A base-class pointer reveals only the base class's view of an object; derived members exist in memory but stay hidden through that pointer.
  • Copying a derived object into a base-class object keeps the base part and drops the derived members, because the base class has no place for them.
  • The usual remedy is to delete both the default copy and the default move operations, with each base class checked on its own terms.

Related concepts

More concepts starting with B