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.
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
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.
- 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.
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.
- 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.
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 likeConcluding that a Shape class needs no suppression because the compiler stops a move with an error.
Why it failsThe source's reassurance applies to one setup, a class with an explicitly declared destructor. It is not a general guarantee.
Do this insteadSuppress copy and move deliberately with =delete when the design forbids them.
Common mistake Treating =delete as useful only for base classes
Why it failsThe source states that =delete is general enough to suppress any operation.
Do this insteadApply =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 likeAssigning a Derived object to a Base object, which keeps only the base part.
Why it failsCopying a base class object in a hierarchy is usually undesirable, and the derived part is lost.
Do this insteadUse 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 failsMember-wise copying produces a second handle, but does not define what the copy means for the resource.
Do this insteadDecide the copy behavior deliberately, and suppress the copy with =delete if that is the design.
Comparing the Destructor Case with the General Rule
| Situation | What the source reports | What you can rely on |
|---|---|---|
| Shape with an explicitly declared destructor, move attempted | Compiler error, because no move is implicitly generated | The error occurs in this setup only |
| Shape with an explicitly declared destructor, copy attempted | Compiler warning, because implicit copy generation is deprecated | A warning, not a guarantee of rejection |
| Suppression written with =delete | The class states the rejection directly | The 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
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.