The Rule of Five & Copy/Move Semantics In Depth

Course 3 · Ch 3
The Rule of Five & Copy/Move Semantics In Depth
The reference table every C++ programmer eventually looks up

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 declareMove ctor/assignmentCopy ctor/assignment
Nothingauto-generatedauto-generated
Destructorsuppressedstill generated (deprecated, legacy-only)
Copy ctor or copy assignmentsuppressedthe other is still auto-generated
Move ctor or move assignmentthe other is still auto-generatedsuppressed entirely

= default and = delete

Modern C++11 syntax to be explicit about intent, rather than relying on implicit suppression rules.

Circle(const Circle&) = default; // explicitly request the compiler-generated version Circle(const Circle&) = delete; // explicitly forbid copying — a compile error at any call site

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.

ConceptC++Rust
Default behaviorcomplex, order-dependent generation/suppression rulesmove by default; Copy/Clone must be explicitly opted into
Explicit intent= default / = delete, optional#[derive(...)], required to opt in
Rules to memorizea real, non-trivial suppression tablenone — one consistent default
Prefer explicit = default / = delete over relying on implicit rules
Stating intent directly in a class's own declaration makes it readable without requiring the reader to have memorized this chapter's own suppression table.
Silently suppressed move operations are a real, quiet performance regression
A class that unintentionally has its move operations suppressed doesn't produce a compile error — it silently falls back to (potentially expensive) copying instead. Not a bug that announces itself; a real class of performance issue worth knowing to look for.

Coding Challenges

Challenge 1

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 solution
Challenge 2

Write 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 solution
Challenge 3

Explain 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 solution

Chapter 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