C++ Virtual Functions and vtables: How Dynamic Binding Works [#33-1]
이 글의 핵심
Explain polymorphism: Base* to Derived object, vptr to per-class vtable, virtual destructor for delete through base pointer, NVI and factory patterns, and devirtualization with final.
Introduction: answering “what is a virtual function?” in interviews
Stopping at “for overriding” invites follow-ups: how does the right function run at runtime, where do vtables live, what is vptr? This article explains vtable layout, object memory, and dynamic binding so you can describe p->virt() through a Base* pointing at a Derived.
Static vs dynamic binding
- Non-virtual member calls use the static type of the pointer/reference.
- Virtual calls use the dynamic type of the object—implemented via vptr → vtable.
Base* p = new Derived();
p->normal(); // Base::normal — not virtual
p->virt(); // Derived::virt — virtual
vtable and vptr
Each polymorphic class has a vtable (list of function pointers + special entries such as RTTI/destructor slots per ABI). Each object stores a vptr to its class’s vtable. Virtual call = load vptr, index slot, indirect jump.
GCC: -fdump-class-hierarchy shows class hierarchy and vtable layouts.
Virtual destructor
Deleting through Base* without a virtual destructor runs only Base::~Base—undefined behavior and leaks derived resources. Rule: polymorphic base ⇒ virtual ~Base().
Common mistakes
- Non-virtual destructor on polymorphic base.
- Default arguments on virtual overrides—statically bound to the static type’s defaults.
- Virtual calls from ctor/dtor—dynamic type not fully
Derived. - Missing
override—accidental overload hiding. - Object slicing when passing by value.
- Exceptions escaping destructors.
override / final
| Keyword | Purpose |
|---|---|
| virtual | Enable overriding in derived classes |
| override | Explicit override; mismatch errors |
| final | No further override / inheritance |
Multiple inheritance
Each polymorphic subobject may have its own vptr; pointer adjustments when casting between bases.
Pure virtual / abstract classes
= 0 makes a class abstract; concrete derived types must implement all pure virtuals.
Performance
Virtual calls cost indirection and usually no inlining on that path. Devirtualization possible when the concrete type is provably fixed (e.g. final, LTO). Prefer measurement over blanket avoidance of virtual.
Interview answers (short)
- Virtual function: mechanism for dynamic binding through base pointers/references.
- vtable: per-class table of addresses for virtual/overrides and related slots.
- vptr: per-object pointer to the vtable for the object’s dynamic type.
Related posts
- VTable guide
- Virtual functions
- Inheritance and polymorphism
Next: Copy vs move #33-2
Previous: STL cheatsheet #32-3