Ch18: Virtual
What This Chapter Is For
This chapter is about new, delete,
::std::unique_ptr, virtual and RTTI. These are the four most
overused features in C++, and the four that C++ tutorials introduce first. We introduce
them here for one reason only: you have to read legacy code. Almost
every C++ codebase you will be paid to touch is built out of them.
We do not use them in new code, and this chapter says exactly why, with measurements. The short version:
| Legacy feature | What it actually costs | What to write instead |
|---|---|---|
new / delete |
Manual lifetime, invisible failure path, failure arrives as the legacy
::std::bad_alloc exception |
An owner type with a destructor; containers that own their storage |
::std::unique_ptr<T> |
Heap round trip per object, plus a pointer that does not say what
T means at the call site |
A value type; type erasure when you really need one type for many representations |
virtual |
A pointer in every object, a table per class, every virtual function emitted
whether called or not, __cxa_pure_virtual, RTTI, a non-portable
ABI |
Template dispatch, or a function-pointer table that you own |
typeid / dynamic_cast |
Type-info objects per class, mangled names, a runtime search per cast | Nothing: with type erasure there is nothing to cast to |
class-level operator new |
Allocates policy hidden inside a type, matched-delete traps | A factory function or an arena you pass in (Ch18.7) |
| Qt / MFC style GUI | Deep virtual hierarchies, new everywhere, one framework ABI for
your whole program |
A web front end in front of a C++ service (Ch21: PWAs) |
The rules in
$HOME/libraries/Portable-Cpp-Guideline are the source of this position:
avoid new, avoid ::std::unique_ptr,
use type erasure over virtual functions for polymorphism, and prefer a
C-style pseudo v-table singleton over the C++ v-table.
Why We Are Against Them
One paragraph each, so the rest of the chapter has something to prove:
newis not a language primitive for allocation, it is a policy decision baked into your source. It commits you to one heap, chosen by the implementation, and it reports failure by throwing a type from 1998 (Ch18.1). Allocation failure is not recoverable and should simply terminate; making it an exception is one of the biggest historical mistakes in the language (Ch18.7).::std::unique_ptris not a smart pointer, it is a pointer with a destructor. It fixes leaks and nothing else: not type confusion, not vptr injection, not ownership ambiguity, not the ABI.virtualis a dispatch table you cannot see, cannot control, and cannot strip. The compiler emits every virtual function of a used class, whether or not anything calls it, and the Itanium ABI adds__cxa_pure_virtualand RTTI on top (Ch18.4).- Type erasure does the same job with values. One type, many representations, no inheritance, no RTTI, no allocation in the user's code, and the bookkeeping that usually goes wrong (freeing twice, using after free, mixing up types, overwriting a vptr) has no room to go wrong (Ch18.6).
The Contents of This Chapter
- Ch18.1:
new,delete, and::std::unique_ptr— what they do, what they cost, and whyvector<unique_ptr<shape>>is the shape of the problem - Ch18.2: What
virtualReally Is — vptr, vtable, dispatch,sizeof, and the reason virtual only pays off with allocation - Ch18.3: Mark Every Virtual Function
—
virtual,overrideorfinal, always, plus anoexceptorthrowscontract - Ch18.4: Dead Virtual Function Bloat
— the 12 KB of code the compiler emits for a function nobody calls, and
__cxa_pure_virtual - Ch18.5: RTTI,
typeidanddynamic_cast— what the type-info objects cost, and why the only safety net RTTI offers is anullptrnobody checks - Ch18.6: Type Erasure Instead — one value type for many representations, a function-pointer table with designated initializers, and the four bug classes that disappear
- Ch18.7: Class-Level
operator newandoperator delete— how to write them if you must, and why you should not want to - Ch18.8: Qt, MFC, and Why GUIs Are Where C++ Went Wrong — the most overused feature in the most overused domain, and the PWA alternative
How to Read This Chapter
Read 18.1 to 18.5 as recognition training: you are learning to see these constructs in other people's code and to know what each one is buying and costing. Read 18.6 as the chapter you actually use. Read 18.7 only on the day you must allocate through a type. Read 18.8 before you accept a job that says "Qt developer".
Everything here is a rule about your code. You will still have to call
libraries that use virtual; that is fine, and the
fast_io device layer does it too. What you must not do is build new
abstractions on top of it.