Exception Handling

Course 2 · Ch 6
Exception Handling
cpp1-5's own exception safety preview, shown in full — and the strongest guarantee still belongs to Rust

cpp1-5 previewed exception safety briefly. This chapter delivers it in full — and closes with the honest, three-way comparison this whole course has been building toward.

C's Total Absence of Error Propagation

Worth naming directly, for the first time: C has no exceptions, no Result type, nothing beyond a manual return code or errno that the caller must remember to check, every single time, with nothing enforcing that check at all.

throw, try, catch

try { throw std::runtime_error("something went wrong"); } catch (const std::exception &e) { std::cout << "Caught: " << e.what() << std::endl; }

Stack Unwinding

When an exception is thrown, the stack unwinds: every function between the throw site and the matching catch exits, and every local object along the way has its destructor called, in reverse order of construction. This is exactly cpp1-5's own "Exception Safety, Briefly" preview, now shown in full.

RAII's Role in Exception Safety

The real payoff: because destructors run during unwinding, RAII-managed resources — smart pointers, containers, file handles wrapped in RAII types — are cleaned up automatically even when an exception is thrown mid-function, with zero additional code from the programmer. A raw resource acquired with new/malloc and manually freed at a function's end leaks if an exception is thrown before that manual free line is ever reached — a genuine, concrete argument for using RAII wrappers pervasively, not just a style preference.

Standard Exception Types

std::exception is the base type, with std::runtime_error, std::out_of_range, and others derived from it. Catch by reference (const std::exception&) to avoid slicing — a real forward pointer to cpp3-1's own object-slicing material.

Exceptions vs. Return Codes vs. Rust's Result<T, E>

Three real, genuinely different guarantees. C's manual return codes/errno: nothing enforces checking them — silently ignorable. C++ exceptions: an uncaught exception terminates the program rather than silently continuing with a wrong value — genuinely stronger than C — but which exceptions a function might throw is invisible in its own signature, a real, honest downside. Rust's Result<T, E>: the possibility of failure is part of the function's own type, and the compiler forces the caller to at least acknowledge it — via ?, .unwrap(), or a match — genuinely the strongest of the three.

ConceptCC++Rust
Failure signalingmanual return code / errnoexceptions — throw/catchResult<T, E>
Ignoring failure silentlyfully possible — nothing enforces a checkuncaught exception terminates the programcompile-time forced acknowledgment
Visible in the function's own typenono — throw specifications aren't part of the signatureyes — Result<T, E> is the return type itself
RAII isn't just convenient — it's the actual exception-safety mechanism
Prefer RAII wrappers specifically because of exception safety, not merely to avoid writing manual cleanup code — a concrete, practical reason beyond convenience.
A destructor that throws during unwinding can crash the whole program
If a destructor itself throws while the stack is already unwinding from another exception, the result is a call to std::terminate — the entire program crashes outright. Destructors should essentially never throw; a real, important C++ idiom.

Coding Challenges

Challenge 1

Write a function that throws a std::runtime_error if a passed-in int is negative, and a try/catch block in main that calls it with a negative value, printing the caught exception's message via e.what().

📄 View solution
Challenge 2

Write a class with a destructor that prints a message, create an instance inside a try block, throw an exception after creating it, and catch the exception in main. Observe when the destructor's message actually prints relative to the catch block.

📄 View solution
Challenge 3

Explain precisely why Rust's Result<T, E> is described as the strongest of the three failure-signaling guarantees compared in this chapter, tying your answer to what specifically is and isn't visible in a function's own type signature in each language.

📄 View solution

Chapter 6 Quick Reference

  • throw/try/catch — C++'s error-propagation mechanism, entirely absent from C
  • Stack unwinding — every local object's destructor runs, in reverse order, between the throw and the catch
  • RAII resources clean up automatically during unwinding — a raw resource with manual cleanup leaks on an exception
  • Catch by const std::exception& to avoid slicing (cpp3-1)
  • C: silently ignorable failure. C++: terminates if uncaught, but invisible in the type signature. Rust: forced compile-time acknowledgment via Result<T, E>
  • Destructors should never throw — doing so during unwinding calls std::terminate
  • Next chapter: lambda expressions — closures in C++, contrasted with Rust's own