Ch20.8: RTTI and dynamic_cast

中文(简体)

1. The Feature

Run-time type information is what makes typeid, dynamic_cast and handler matching work. P1105R0 §4.3 proposes making it optional in freestanding mode, for the reason you can probably guess: it is a table per class, a string per type name, and a runtime function that has to walk a class hierarchy.

The RTTI for a single class is a type_info object, which for a class with a base is a __si_class_type_info, which has a vtable of its own, and the whole structure is described by the C++ ABI rather than by the standard. So the cost is not "a few bytes of metadata": it is a second vtable per class, plus a runtime that knows how to compare and walk those objects.

2. Measured: What One dynamic_cast Requires

struct B { virtual ~B() noexcept = default; };
struct D : B { int v{}; };

int probe(B &b) noexcept {
  if (dynamic_cast<D *>(&b) != nullptr) { return 1; }
  return 0;
}
$ g++ -std=c++26 -fno-exceptions -O2 -c rtti.cc && nm -u rtti.o
                 U _ZTVN10__cxxabiv117__class_type_infoE
                 U _ZTVN10__cxxabiv120__si_class_type_infoE
                 U __dynamic_cast
$ g++ -std=c++26 -fno-exceptions -O2 -nostdlib rtti.cc -o rtti
  undefined reference to `__dynamic_cast
  undefined reference to `vtable for __cxxabiv1::__class_type_info
  undefined reference to `vtable for __cxxabiv1::__si_class_type_info

Three symbols for nine lines, all of them from the C++ runtime library (libsupc++ or libc++abi), none of which is freestanding. And note that this is with -fno-exceptions already applied: RTTI is a separate dependency, not a consequence of exceptions. That is why the ban list in 19.7 contains -fno-rtti as well.

$ g++ -std=c++26 -fno-rtti -O2 -c rtti.cc
rtti.cc:5:7: error: 'dynamic_cast' not permitted with '-fno-rtti'

3. Why the Cost Is Worse Than It Looks

What RTTI costsWhere it lands
A type_info object per polymorphic class Read-only data, with the class's name as a string in the binary
A second vtable per derived class Data plus relocation, one per class in the program
An ABI-defined runtime entry point __dynamic_cast: cannot be written portably, cannot be omitted if used
Handler matching for legacy exceptions Every catch clause compares type information at run time
Compiler support for typeid on every type you name Even when nothing casts, a typeid expression drags the object in

On a desktop none of this matters. On a board with 64 KiB of flash and a 20 KiB budget for your class hierarchy, the type name strings alone can be a real number. Which is why freestanding builds turn it off, and why "just use dynamic_cast" is not advice you can give someone targeting an MCU.

4. The Bigger Argument, Which Is Not About Size

This book already argued (18.5) that dynamic_cast is the wrong tool even where it works: it is far more expensive than typeid, its failure mode is a null pointer that nobody checks, and the code that avoids it uses unchecked static_cast instead, which is where type confusion comes from. The fix is not more RTTI, it is knowing the type statically — closed hierarchies, tags, variants, or reflection.

Freestanding makes that argument sharper, because it removes the temptation entirely. If RTTI is not available, you cannot write the design that depended on it, and you will write the one where the type is known at the call site. That is not a compromise; it is the design that was going to be faster and clearer anyway.

5. What Replaces It

Notice that every replacement is freestanding by construction, because none of them needs a runtime that knows your class names.