5.2.29 · D1C++ Programming

Foundations — Exception safety — basic, strong, no-throw guarantees

1,872 words9 min readBack to topic

Before you can reason about "basic vs strong vs no-throw", you need a handful of building blocks that the parent note quietly assumed. We build each one from nothing: plain words → a picture → why the topic needs it.


1. What is a "function call" as a picture — the call stack

Figure — Exception safety — basic, strong, no-throw guarantees

Look at the figure. main sits at the bottom. It called f, which stacked a box on top; f called g, another box; g called new (memory allocation), the top red box. The only box that can run right now is the top one. Everything below is paused, waiting for the box above it to finish.


2. What is an exception — and "throwing"

The word throw is literal: picture the top box tossing a ball straight down the stack, box by box, looking for someone who agreed to catch it (a try/catch). Until it is caught, every box it passes through is destroyed.


3. Stack unwinding + destructors — the engine of cleanup

Figure — Exception safety — basic, strong, no-throw guarantees

In the figure, the red exception rises from g up toward main. As it passes each box, that box's destructors fire (the small marks). This is the one mechanism C++ gives you for guaranteed cleanup — and it is the foundation of every guarantee we will study.


4. Resource, leak, invariant — the three words the guarantees are about


5. RAII — tying a resource to a box

Figure — Exception safety — basic, strong, no-throw guarantees

The figure shows the pairing: the moment the object is born (top), it grabs the resource; the moment its box pops (bottom), the destructor hands the resource back. There is no gap where an exception could sneak past and skip cleanup.


6. Smart pointers — RAII for memory specifically

Instead of writing new (acquire) and remembering a matching delete (release) — which unwinding can skip — you write std::make_unique<Widget>() once and the destructor guarantees the delete. That is RAII applied to the most common resource: memory. See Smart pointers — unique_ptr, shared_ptr.


7. noexcept — the promise "this never throws"


8. Swap — the atomic "commit" move

Figure — Exception safety — basic, strong, no-throw guarantees

Look at the figure: a and b each hold a pointer to a buffer. Swap merely re-labels which pointer belongs to whom — no memory is copied, nothing can run out. Because it can't throw, swap is the perfect last, safe instant to commit changes. This is the heart of Copy-and-swap idiom.


9. Move semantics — why the standard library watches your noexcept


How it all feeds the topic

call stack of paused jobs

exception thrown

stack unwinding

destructors run

RAII no leaks

smart pointers

BASIC guarantee

swap is cheap

swap is noexcept

noexcept keyword

copy and swap

STRONG guarantee

NO-THROW guarantee

move semantics

move if noexcept in vector


Equipment checklist

Recall Self-test: are you ready?

A call stack is a stack of what, added and removed in what order? ::: A stack of function frames (boxes of locals), added and removed top-first (last-in first-out). What does throw do to the normal flow of a function? ::: It abandons the function immediately and sends an exception object upward through the stack instead of returning normally. What is stack unwinding? ::: The runtime popping frames off the top one by one until a catch is found, running destructors as each frame is popped. When exactly does a destructor run? ::: The instant an object's frame is popped — whether by normal scope exit or by unwinding. Define a resource, a leak, and an invariant. ::: Resource = something you acquire and must release; leak = acquired but never released; invariant = a rule that must always hold about an object. What does RAII glue a resource's lifetime to? ::: The lifetime of a stack object — acquire in constructor, release in destructor. What does a std::unique_ptr guarantee about its owned memory? ::: Its destructor runs delete automatically when the pointer's frame is popped, so the memory is freed even during unwinding. What happens if a noexcept function actually throws? ::: The runtime calls std::terminate() and the program dies immediately with no unwinding. Why can swapping two pointer-holding objects never throw? ::: It just exchanges a pointer and a size — a few assignments, no allocation, nothing that can fail. Why does std::vector copy instead of move when a type's move is not noexcept? ::: A throwing move could gut the old buffer halfway and destroy the strong guarantee, so it copies to keep the source intact.