Polymorphism allows us to write generic interfaces where base class pointers invoke derived class overrides at runtime. In languages like Java, all class methods are polymorphic by default. In C++, polymorphism is opt in (using the virtual keyword) because runtime dynamic dispatch adds physical memory and execution overhead.
Let's look at how polymorphism is implemented in memory and the critical rule of virtual destructors.
The Virtual Table (vtable) and Virtual Pointer (vptr)
Most mainstream C++ implementations resolve virtual calls with a hidden virtual table (vtable) and a per-object pointer (vptr) to it. The C++ standard specifies the observable virtual-dispatch behavior, not this representation or where any hidden field appears in object layout.
Use a vtable diagram as a common implementation model, not a portable binary-layout contract. ABI boundaries, compiler options, and inheritance shape can all affect the generated representation.
The Cost of Dynamic Dispatch
When you call a virtual function, the CPU cannot jump directly to the instruction block. Instead, it must execute three separate steps:
- Dereference the object's
vptrto find the address of thevtable.
- Index into the
vtableto locate the target function pointer.
- Perform an indirect call to that address.
This vtable lookup adds latency and prevents the compiler from performing inline optimizations. More importantly, it destroys CPU Branch Prediction.
A virtual call can inhibit inlining and may be harder for branch prediction when dynamic targets vary. It is often fine outside a measured hot loop, and even in hot code the right choice depends on data layout, call distribution, and readability. Replace polymorphism only after profiling identifies it as material.
- The Switchboard Metaphor: Calling a standard function is like dialing a direct number. Calling a virtual function is like calling a 1950s switchboard operator. You have to wait for the operator to look up the name in a directory book (
vtable) and plug the wire in, adding connection delays to your execution path.
Polymorphism & Virtual Table (vtable) Dispatch
Step-by-step walkthrough showing pointer redirections during virtual calls.
Step 1: The Virtual Call Trigger
An invocation of a virtual function (like base pointer delete ptr) occurs. The CPU looks at the heap object, which contains a hidden virtual table pointer (_vptr) at its starting address (offset 0).
The Critical Hazard: Virtual Destructors
Deleting a derived object through a base pointer whose destructor is not virtual is undefined behavior. A common symptom is skipped derived cleanup, but no particular outcome is guaranteed:
To fix this hazard, always declare destructors as virtual in any class you expect developers to inherit from. This guarantees the destructor chain runs in the correct order: from the derived class up to the base class.