Skip to content
UniCircle
MediumConceptualPolymorphism

How do virtual functions work, and why do base classes need a virtual destructor?

Question CPP-OOP-001

Explain how C++ decides which function to call for base_ptr->draw() when draw is virtual. What goes wrong if a polymorphic base class has a non-virtual destructor?

Show the explanation

Static vs dynamic binding. For a non-virtual function, the compiler picks the function from the static type of the pointer (Base*), at compile time. For a virtual function, the choice happens at run time from the dynamic type: the actual class of the object the pointer refers to.

How compilers usually implement it. The standard does not require this layout, but every mainstream compiler uses it:

  • Each class with virtual functions has a vtable, a table of function pointers with one slot per virtual function. Each slot points to that class's override.
  • Each object of such a class carries a hidden vptr that points to its class's vtable. The constructor sets it.
  • p->draw() compiles to roughly: load the vptr, load the draw slot, call it. That is one or two extra memory loads per call, and the call usually cannot be inlined.

Virtual destructor

C++
struct Base { ~Base() {} };                  // not virtual
struct Derived : Base { std::vector<int> big; };
Base *p = new Derived;
delete p;                                    // undefined behaviour

Deleting a derived object through a base pointer whose destructor is not virtual is undefined behaviour. In practice Derived's destructor does not run, so big leaks. Rule: if a class is meant to be used polymorphically (it has any virtual function), give it a public virtual destructor, for example virtual ~Base() = default;. If you never want deletion through a base pointer, make the destructor protected and non-virtual instead.

Related points

  • Calling a virtual function from a constructor or destructor does not dispatch to the derived class. During base construction the object is still only a Base.
  • override and final let the compiler check your intent.
  • A pure virtual function (= 0) makes the class abstract.
  • Polymorphism needs references or pointers. Passing by value slices the object down to the base part.

Original UniCircle question. Last reviewed 22 Oct 2026.