Skip to content
UniCircle
HardConceptualPolymorphism internals

How virtual functions work: vtables and dynamic dispatch

Question OOP-109

In C++, how does a call through a base-class pointer reach the correct derived-class function at run time? Explain the vtable/vptr mechanism, its costs, and why a base class with virtual functions should usually have a virtual destructor.

Show hints

Each polymorphic object carries one hidden pointer.

Show the explanation

Model answer. The C++ standard only specifies the behaviour of virtual functions; virtually every compiler implements it with virtual tables.

The mechanism

  • For each class that has virtual functions, the compiler creates a vtable: a table of function pointers, one slot per virtual function, pointing to the implementation that class uses (its own override or the inherited one).
  • Each object of such a class contains a hidden pointer, the vptr, set by the constructor to point to its class's vtable.
  • A call basePtr->draw() compiles roughly to: load the vptr from the object, index the slot for draw, and call through that function pointer. Because a Circle object's vptr points to Circle's vtable, Circle::draw runs even though the pointer's static type is Shape*.
  • Non-virtual functions are bound at compile time and do not use the vtable.

Costs

  • Memory: one vptr per object (8 bytes on typical 64-bit systems) plus one vtable per class.
  • Time: an extra indirect load and indirect call. More significant is that the compiler usually cannot inline virtual calls unless it can prove the dynamic type (devirtualisation, helped by final).

Virtual destructors

C++
Shape* s = new Circle();
delete s;

If Shape::~Shape() is not virtual, deleting a derived object through a base pointer is undefined behaviour — in practice Circle's destructor typically does not run, leaking whatever it owned. Declaring virtual ~Shape() = default; makes destruction dispatch through the vtable, so ~Circle() runs first, then ~Shape().

Subtle point: virtual calls in constructors and destructors
While Shape's constructor runs, the object is still only a Shape (the vptr points to Shape's vtable), so a virtual call inside it invokes Shape's version, not Circle's. Calling a pure virtual function this way is undefined behaviour.

Follow-ups an interviewer may ask

  • "What does sizeof return for an empty class vs an empty class with one virtual function?" Typically 1 vs 8 on a 64-bit compiler (implementation-defined).
  • "How does Java compare?" All non-static, non-private, non-final Java methods are virtual by default; the JVM uses similar method tables, and the JIT aggressively inlines monomorphic calls.

Original UniCircle question. Last reviewed 28 Oct 2026.