5.2.29 · D3C++ Programming

Worked examples — Exception safety — basic, strong, no-throw guarantees

3,303 words15 min readBack to topic

The scenario matrix

Just like an angle formula has quadrants where the naive version fails, exception safety has "cells" — recurring situations that each demand a different answer. The figure below is our board: each cell is a square, coloured by which tool fixes it, exactly the way you'd chart the four quadrants of an angle.

Figure — Exception safety — basic, strong, no-throw guarantees
The scenario board. Each square is one cell (C1–C9); its colour says which tool applies.

Here is the full board in words:

Cell Situation The core question
C1 Single resource, may throw after acquire Does cleanup still happen?
C2 Two resources, the second acquisition (allocation) throws Is the first one leaked?
C3 Multi-step visible mutation, later step throws Are earlier steps rolled back?
C4 Copy-and-swap upgrade of C3 Can we make it strong?
C5 The "zero / degenerate" input (empty, self, no-op) Does the safe path survive trivial input?
C6 Move that throws mid-transfer Is strong lost? Copy vs move.
C7 noexcept that lies and throws What actually happens at runtime?
C8 Real-world word problem (bank transfer) Which guarantee does the business need?
C9 Exam twist: which guarantee, and why not stronger? Reason about the whole, not the parts.

Below, each example is tagged with the cell(s) it covers. Together they fill the whole board.


Example 1 — Single resource (Cell C1)


Example 2 — Two resources, second throws (Cell C2)


Example 3 — Multi-step visible mutation (Cell C3)


Example 4 — Upgrade C3 to strong (Cell C4)


Example 5 — Degenerate inputs (Cell C5)


Example 6 — Move that throws (Cell C6)


Example 7 — A noexcept that lies (Cell C7)


Example 8 — Real-world word problem (Cell C8)


Example 9 — Exam twist (Cell C9)


Recall Which cell demands which tool?

C1/C2 → RAII / Smart pointers — unique_ptr, shared_ptr. C3→C4 → Copy-and-swap idiom. C6 → noexcept moves + std::vector reallocation strategy. C7 → honest noexcept. C8 → strong for domain invariants. C9 → whole = weakest path.

Active recall

Why does the bank transfer need strong, not basic?
Because basic keeps objects valid but lets the debit persist while the credit fails — money is not conserved; the domain invariant is broken.
In Example 2, exactly how many Widgets leak and why?
One — a's raw pointer has no destructor, so unwinding never frees it after b's new throws.
Why is copy-and-swap self-assignment-safe automatically?
The right-hand copy is made by pass-by-value before the body runs, so *this and rhs are always distinct objects.
What does a std::vector do when T's move constructor is not noexcept?
It copies elements during reallocation (move_if_noexcept), because a throwing move would gut the source and lose the strong guarantee.
Whole-function guarantee of a multi-path function equals what?
The weakest guarantee among all reachable paths.