The Rule of Five & Copy/Move Semantics In Depth
cpp1-5 and cpp2-8 both previewed the Rule of Five. This chapter is the full mechanical depth — exactly which declaration suppresses which auto-generation.
The Five Special Member Functions, Named Precisely
- Destructor —
~T() - Copy constructor —
T(const T&) - Copy assignment —
T& operator=(const T&) - Move constructor —
T(T&&) - Move assignment —
T& operator=(T&&)
What the Compiler Generates by Default
If none are declared, the compiler generates all five automatically — each doing the "obvious" member-wise thing. This default is genuinely correct for classes made entirely of other well-behaved types. This is the Rule of Zero (cpp2-8), explained mechanically: it works precisely because the compiler-generated defaults are already correct when every member is itself well-behaved.
The Suppression Rules — When Declaring One Function Silently Deletes Others
The real mechanical depth. Declaring a destructor suppresses automatic generation of the move constructor and move assignment entirely — not a bad default, simply not generated at all. Declaring any of copy constructor, copy assignment, move constructor, or move assignment suppresses both move operations from being auto-generated.
A Real Table — What Suppresses What
| You declare | Move ctor/assignment | Copy ctor/assignment |
|---|---|---|
| Nothing | auto-generated | auto-generated |
| Destructor | suppressed | still generated (deprecated, legacy-only) |
| Copy ctor or copy assignment | suppressed | the other is still auto-generated |
| Move ctor or move assignment | the other is still auto-generated | suppressed entirely |
= default and = delete
Modern C++11 syntax to be explicit about intent, rather than relying on implicit suppression rules.
cpp2-4's own unique_ptr uses exactly = delete internally on its own copy constructor — not magic, simply this syntax.
Rust's Approach Contrasted
Rust has no equivalent implicit-generation-with-suppression-rules system at all. Copy/Clone must be explicitly derived or implemented — #[derive(Clone, Copy)] — and move is simply the default for every type unless it opts into Copy. No five-function dance, no suppression table to memorize. A genuinely simpler, more explicit design, deliberately trading C++'s flexibility for predictability.
| Concept | C++ | Rust |
|---|---|---|
| Default behavior | complex, order-dependent generation/suppression rules | move by default; Copy/Clone must be explicitly opted into |
| Explicit intent | = default / = delete, optional | #[derive(...)], required to opt in |
| Rules to memorize | a real, non-trivial suppression table | none — one consistent default |
Coding Challenges
Write a class with a hand-written destructor and nothing else declared. Attempt to move-construct an instance of it, and report whether the move constructor or the copy constructor actually runs (add print statements to both to observe).
📄 View solutionWrite a class that explicitly deletes its copy constructor with = delete, then attempt to copy an instance of it and report the resulting compile error.
📄 View solutionExplain precisely why a class declaring only a destructor still ends up copying (not moving) when passed by value, tying your answer directly to this chapter's own suppression table.
📄 View solutionChapter 3 Quick Reference
- The five: destructor, copy ctor, copy assignment, move ctor, move assignment
- Declare nothing — all five are auto-generated correctly (the Rule of Zero, mechanically explained)
- Declare a destructor — move operations are suppressed entirely; copy is still generated (deprecated)
- Declare any copy or move operation — both move operations are suppressed
= default/= delete— state intent explicitly, rather than relying on implicit rules- Rust has no equivalent system — move by default, Copy/Clone opted into explicitly, no suppression table
- Next chapter: concurrency in modern C++ — std::thread, std::mutex, std::atomic