Concepts / Class Hierarchies and Virtual Functions

Class Hierarchies and Virtual Functions

A base class in a class hierarchy is one clear case of an object we would not want to copy.

  • Programming

The Belief That Missing Suppression Is Harmless

A common line of reasoning goes like this: if you forget to stop a class from being copied or moved, the program will simply behave a little differently, and nothing serious will go wrong. Under this view, suppressing copy and move operations is a nice-to-have rather than something that really matters. This article examines that belief. It is partly true for one specific situation, and misleading as a general rule.

What do you think happens?

Suppose a Shape class has been written so that copying it is suppressed, and then code tries to copy a Shape object. What will the compiler do in that situation? Consider a Shape class that has an explicitly declared destructor.

Reveal answer
Answer:

In the source's case, where the class has an explicitly declared destructor, trying to move a Shape produces a compiler error, and trying to copy a Shape should produce a compiler warning.

Because the destructor is declared explicitly, a move operation is not implicitly generated, so a move attempt fails at compile time. Implicit generation of copy operations is deprecated when a user-declared destructor is present, so a copy attempt draws a warning. This is the safety net the source describes, and it depends on this particular setup.

Why the Safety Net Is Only Partial

The source is specific about when forgetting a copy or move suppression causes little harm. The class has an explicitly declared destructor. That declaration changes what the compiler generates. A move operation is not implicitly generated, so a move attempt on a Shape produces a compiler error. Copy operations are also affected, because their implicit generation is deprecated when a user-declared destructor is present, so a copy attempt should produce a compiler warning.

Notice what this result depends on. The protection comes from the compiler's diagnostics in this particular setup, not from a general guarantee. The source introduces the situation with the words in this particular case, which signals that the reassurance does not extend to every class. A learner who concludes that suppression never matters has taken the exception for the rule.

How =delete States Design Intent

Because =delete is written as part of an operation's declaration, the design intent is visible to anyone who reads the class. The compiler then enforces that intent. Rather than hoping that a misuse will trigger some warning, the class tells the compiler directly which uses should be rejected. This is why the source treats suppression as a deliberate choice rather than an accident of how the class happens to be written.

The mechanism is general. The source states that =delete can suppress any operation, so the same marker that blocks copying can also block moving or any other operation the class wants to forbid. The example below is an illustrative sketch, written for this article, showing where the markers sit in a Shape declaration.

cpp
Notes on the code
  • The =delete marker sits in the operation's own declaration, so the intent is visible to any reader.
  • The default constructor is written explicitly so that Shape a; still compiles.
  • Copy and move operations are each suppressed by their own declaration.

Why a Base Class Object Should Not Be Copied

A base class in a class hierarchy is one clear case of an object we would not want to copy. The source uses a Shape object as its running example. A Shape is meant to serve as the common base for more specific kinds of shapes, so copying a Shape object by value is usually not what the design intends. Suppressing copy and move operations on the base class is how the design expresses that intention.

The reason a by-value copy is problematic can be traced in memory. A Derived object contains a base part and a derived part. When a Derived object is copied into a Base object, only the base part fits, so the derived part is lost. The diagram below shows the state before and after such a copy.

Base partBase partDerived partDerived partnot copiedcopiedlost
When a Derived object is copied into a Base object, which parts of the original are lost, and what does the Base object contain afterward?

Virtual Clone as the Safe Alternative

The source gives the alternative directly: if you need to copy an object in a class hierarchy, write a virtual clone function. The decision to produce a copy moves out of a blanket copy operation and into a function that each derived class can override to produce the right kind of object. Copying remains possible, but it is expressed in a way that fits the hierarchy, rather than slicing or duplicating the base class by accident.

Consider code that holds only a Base pointer. It cannot know which derived type it points to, but it can still ask for a copy. The sequence below shows the calls involved. The code is an illustrative sketch written for this article rather than taken from the source.

Callerholds a Base pointerDerived objectoverrides cloneNew Derived copy1. call clone()2. create a copy of itself3. return as Base pointer
When code holds only a Base pointer, what sequence of calls produces a correctly typed copy of the derived object?
cpp
Notes on the code
  • The base class declares clone as a virtual function, so calls through a Base pointer are dispatched to the derived override.
  • Circle overrides clone to produce a copy of its own, correctly typed, type.
  • The caller never names Circle, yet receives a full copy of the derived object.

Why a Resource Handle Is Not Copied by Its Members

The source makes a second point that is easy to overlook. A resource handle generally cannot be copied just by copying its members. A resource handle is an object that stands for some resource, and its members hold the information needed to manage that resource. Copying those members produces a second handle object, but that alone does not describe what the copy should mean for the resource itself.

Handle AoriginalResourceHandle Bmember-wise copyrefers torefers to, by copied information
After a member-wise copy, how do the two handle objects relate to the resource they manage?

The lesson is that a copy of a resource handle needs a deliberate design. Some designs suppress the copy entirely with =delete, which is the same mechanism used for base classes. The source does not describe every alternative for resource handles, so the key takeaway is simply that member-wise copying is not automatically the correct behavior.

Mistakes Learners Make with Suppression and Copying

  • Common mistake

    Assuming that forgetting to suppress copy or move is always harmless

    What it looks like

    Concluding that a Shape class needs no suppression because the compiler stops a move with an error.

    Why it fails

    The source's reassurance applies to one setup, a class with an explicitly declared destructor. It is not a general guarantee.

    Do this instead

    Suppress copy and move deliberately with =delete when the design forbids them.

  • Common mistake

    Treating =delete as useful only for base classes

    Why it fails

    The source states that =delete is general enough to suppress any operation.

    Do this instead
    Apply =delete to whichever operation the class should reject, including the handle and base class cases in this article.
  • Common mistake

    Copying a base class object by value because the code happens to compile

    What it looks like

    Assigning a Derived object to a Base object, which keeps only the base part.

    Why it fails

    Copying a base class object in a hierarchy is usually undesirable, and the derived part is lost.

    Do this instead

    Use a virtual clone function, overridden by each derived class, when a copy is needed.

  • Common mistake

    Assuming a resource handle is copied correctly when its members are copied

    Why it fails

    Member-wise copying produces a second handle, but does not define what the copy means for the resource.

    Do this instead

    Decide the copy behavior deliberately, and suppress the copy with =delete if that is the design.

Comparing the Destructor Case with the General Rule

SituationWhat the source reportsWhat you can rely on
Shape with an explicitly declared destructor, move attemptedCompiler error, because no move is implicitly generatedThe error occurs in this setup only
Shape with an explicitly declared destructor, copy attemptedCompiler warning, because implicit copy generation is deprecatedA warning, not a guarantee of rejection
Suppression written with =deleteThe class states the rejection directlyThe compiler enforces the stated intent for any operation

The destructor case gives partial reassurance; explicit suppression gives a stated design intent.

Practice Deciding What to Suppress

MEDIUM

A class Shape is the common base for Circle and Square. Your teammate proposes copying Shape objects by value whenever a function needs a copy. Using the ideas in this article, explain what is wrong with that plan and describe what you would write instead.

Hints
  • Think about what the derived part of a Circle becomes when it is copied into a Shape object.
  • Consider the virtual clone function described in the source and where the copying decision lives.
  • Ask whether the base class itself should be copyable at all.

Key Takeaways

  • Forgetting to suppress copy or move is only partly harmless. In the source's case, a class with an explicitly declared destructor gets a compiler error on move and a warning on copy, but this is not a general guarantee.
  • The =delete mechanism is written in the operation's declaration, is general enough to suppress any operation, and makes suppression a deliberate statement of design that the compiler enforces.
  • Copying a base class object in a hierarchy is usually undesirable because the derived part is lost. A virtual clone function, overridden by each derived class, produces a correctly typed copy instead.
  • A resource handle generally cannot be copied just by copying its members, so the copy behavior for a handle requires deliberate design, and member-wise copying is not automatically correct.

Related concepts

More concepts starting with C