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.
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?
Reveal 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.
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.
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.
b.id is 7. There is no member named extra in b.- 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.
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 likeA class with only plain values copies cleanly, so the same habit is applied to a base class that a derived class extends.
Why it failsThe defaults work in ordinary cases, but a base class's default operations reflect only the base class's members.
Do this insteadCheck 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 likeA 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 failsA 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 insteadTreat 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 likeThe copy constructor is deleted, but the move operations are left at their defaults.
Why it failsCopy 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
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.