Ch19: Legacy Exceptions and Herbceptions
What This Chapter Is For
You have been throwing and catching since
Ch3.16, and every page since then has
used the same two statements. What you have not seen is the table behind the error value:
the thing that decides what a code means, what it prints, and which
::std::errc it corresponds to. This chapter is about that table, and about the
other exception mechanism C++ has, which you must be able to read and must not use.
| Mechanism | What it costs | What it costs in this book |
|---|---|---|
| Herbceptions | Two words on the return path plus one failure bit — the same shape as returning an error code | The only mechanism used, everywhere |
| Legacy C++ exceptions | A heap allocation per throw, a two-phase stack walk, frame tables in every function, a personality routine, RTTI for handler matching | Read-only. Removed from every build by flag (Ch19.4) |
The short version of the second row: legacy exceptions are not merely slow (about a
microsecond per throw, measured in
Ch19.8). They are
unavailable on most targets you will ship to — freestanding, kernels,
bootloaders, embedded boards, WebAssembly — because the unwinder, the loader
registration and the __cxa_* runtime they depend on do not exist there.
How to Read This Chapter
Read 19.1 only when you are writing a library that has a code space of its own. Read it carefully anyway, because it starts with the recommendation that saves the most work: the domains in the runtime are meant to be reused, and one of them covers nearly everything.
Read 19.2 first: it decides what
counts as an error at all, and everything else in the chapter follows from it. Then read
19.3,
19.4 and
19.5 as recognition
training for the code you will inherit, and
19.6 as the two directions of
the bridge you will need the day a dependency hands you a std::runtime_error.
19.7 is five flags to copy
into your build right now.
- Ch19.1: Defining and
Overriding Error Domains — the five hooks of
::std::error_domain_singleton, the one that is mandatory, theerror_domain<T>specialization, a complete CSV domain, and how to replace a shipped domain when the environment demands it - Ch19.2:
std::abort(),std::terminate()and__builtin_trap()— why allocation failure, size overflow and a broken invariant are not errors, the three ways to end a process, and what fast_io crashes on - Ch19.3: C++ Legacy
Exceptions — how the mechanism works and how to read it: what a throw copies,
what a handler matches, what unwinding destroys, and what
exception_ptrkeeps alive - Ch19.4:
<stdexcept>— thelogic_error/runtime_errortree, whatwhat()actually returns, and the slicing bug the header invites - Ch19.5:
<system_error>—error_code,error_categorywith its vtable and its ABI scar, and why the design fragmented instead of unifying - Ch19.6: Crossing the
Boundary — legacy to herbceptions is automatic, herbceptions to legacy is
throw_legacy_exception(), and what is lost on the way - Ch19.7: Ban Legacy Exceptions
With Your Build Flags —
-fno-exceptions -fno-rtti -fno-unwind-tables -fno-asynchronous-unwind-tables -fno-ident, and what each one deletes - Ch19.8: The Issues With Legacy C++ Exceptions — the measured cost, the runtime it requires, the environments that do not have it, the unsound corners, and the two ABIs
- Ch19.9: fast_io's
Allocator Flags — the allocator is the policy: how to make a fast_io container
report
::std::errc::not_enough_memoryinstead of terminating, and why the standard allocator model cannot express any of it - Ch19.10: De-fragmenting C++ — Herb Sutter's CppCon 2019 talk, the source of everything this chapter taught