Course · C++

How a vtable works

Jun 25, 20265 min

A free lesson from the course “C++ without Memory Allocations” on Stepik.

Polymorphism in C++ is achieved with virtual functions. It allows calling different implementations of the same function through a pointer or reference to a base class. Thanks to this, you can work with objects of different types uniformly, while the actual implementation is selected automatically at run time.

class Base
{
public:
    const char* name() { return "Base"; }
};

class Child: public Base
{
public:
    const char* name() { return "Child"; }
};

int main()
{
    Child child;
    Base &obj = child;
    std::cout << "Object is a " << obj.name();
}

For this to work, the compiler uses a special mechanism — the vtable (virtual table). If a class has at least one virtual function, the compiler creates a special structure that stores the addresses of the virtual functions.

Every object of such a class receives a hidden pointer vptr that points to the corresponding vtable. If a virtual function is not overridden in a derived class, then the address of the base class implementation is written into the derived class's vtable.

When we call a virtual function through a pointer or reference to a base class, the following is used:
vptr → vtable → address of the required function.
Thanks to this, the call is bound not at compile time but at program run time.

Thus, the vtable mechanism provides dynamic binding and makes polymorphism in C++ possible

Our own vtable implementation

To understand how this works, let's implement the example above using function pointers.

struct Base
{
    void (*name)(Base* me);
};

struct Child
{
    Base v; // model inheritance
};

void child_name(Base *ap);

// works like a class constructor
void init_child(Child *ap)
{
    ap->v.name = &child_name;
}

The compiler generates slightly different code, since it stores the function pointer in a global virtual table (usually called a "vtable"). The way the compiler does this looks roughly like this:

// Forward declaration of the virtual table structure
struct base_vtable;

struct Base
{
    // Note: this is a *pointer* to a vtable, not an actual instance
    // This is needed for our polymorphism implementation - each object stores only a pointer,
    // not a copy of the entire function table
    const base_vtable * vtable;
};

struct base_vtable
{
    // The virtual table stores pointers only to functions
    // This is the foundation of the virtual function mechanism in C++
    // Each virtual function is represented by a pointer in the vtable
    void (*name)(Base* me);
};

void base_name(Base * v)
{
    // Note that the actual name() function that will be called
    // depends on what is stored in the vtable of the specific object
    v->vtable->name(v);
    // This is called dynamic dispatch - the decision is made
    // at program run time, not at compile time
}

struct Child
{
    // Modeling inheritance - the "base class" interface is the first member
    // This guarantees that a pointer to child can be safely cast to a pointer to base,
    // since both pointers will point to the same address in memory
    Base v;
};

// Prototype of the virtual function name() for the child class
void child_name(Base * ap);

// The virtual table for the child type links the name() call to the child_name() function
// This vtable is static (const) and is created once at compile time
// All objects of the child type will point to this same vtable
const base_vtable child_vtable = {
    .name = &child_name  // for explicit initialization
};

// When creating child objects, you must initialize the vtable
// so that the appropriate virtual functions are called
// This is the equivalent of a constructor in C++ - it sets the vtable pointer
// to the correct table for this type
void init_child(Child *ap)
{
    // Important step: link the object to its vtable
    // After this, base_name() calls will automatically call child_name()
    // thanks to the indirection through the vtable pointer
    ap->v.vtable = &child_vtable;
}

When we call a virtual function in C++, there are usually two pointer dereferences:

  1. First, through the hidden pointer vptr the object finds its virtual table (vtable).

  2. Then the address of the required function is taken from that table, and the call is made at that address.

At first glance this seems like an "expensive" operation, but in practice the compiler can heavily optimize such calls. For example:

So why bother with all this?

First, the vtable mechanism guarantees that calls will work the same way regardless of the specific class we use through a pointer to the base.

Second, it provides uniform behavior for pointers to member functions: the compiler has a single way to store and call them, regardless of whether it is an ordinary function or a virtual one. Despite the extra steps, the vtable makes polymorphism in C++ both convenient and predictable, and in most cases the price for this is minimal thanks to optimizations.

Take the course on Stepik →

← All articles