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 costs | Where 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
-
Closed hierarchies with a discriminator you control — an enum in
the object, or a tag type (15.3).
Costs one integer, works everywhere, and the
switchis exhaustive-checked by the compiler. - Type erasure with a known function table (18.6) — the operations are in the table, so no cast is needed at all.
- Static polymorphism: templates and concepts (Ch15) resolve the type at compile time, which is where it was known all along.
-
Reflection, when it exists, is the real answer to
dynamic_cast's problem: it gives you the member by name without a hierarchy walk. Until then, the tag is the tool.
Notice that every replacement is freestanding by construction, because none of them needs a runtime that knows your class names.