In C++, polymorphism is commonly achieved using virtual
functions. But how does the compiler implement this mechanism under the hood? The answer is usually a vtable, or virtual table. This article explains what vtables are, how they work, and why they're important for polymorphism in C++.
What is a VTable?
A vtable is an internal lookup table created by the compiler to support dynamic dispatch of virtual functions. It holds pointers to the virtual functions of a class, allowing the correct function implementation to be called at runtime based on the object's actual type.
In simpler terms, the vtable lets C++ decide which version of a virtual function to call — even when using a pointer or reference to a base class.
Why Do We Need VTables?
Consider this example:
class Base {
public:
virtual void greet() { std::cout << "Hello from Base\n"; }
};
class Derived : public Base {
public:
void greet() override { std::cout << "Hello from Derived\n"; }
};
void say_hello(Base* b) {
b->greet();
}
int main() {
Derived d;
say_hello(&d);
return 0;
}
Without vtables, the call to b->greet()
inside say_hello
would always invoke Base::greet()
, because the pointer type is Base*
— the compiler resolves function calls at compile time.
Vtables allow the call to dispatch dynamically at runtime to Derived::greet()
because the actual object pointed to is a Derived
, not just a Base
.
How VTables Work Under the Hood
Although vtable implementation details vary across compilers, the general idea is:
- Each polymorphic class (i.e., a class with virtual functions) has a static vtable — an array of function pointers, one per virtual function.
- Each object of that class stores a hidden pointer (called the vptr) pointing to its class's vtable.
- When a virtual function is called on an object, the call is resolved by looking up the function pointer in the vtable via the object's vptr, then invoking that function.
Here’s a rough visualization:
Object memory layout:
+----------------+
| vptr ---------> +---------------------+
+----------------+ | <VTable for Derived> |
+---------------------+
| <ptr to Derived::greet> |
| <ptr to other virtual fns> |
+---------------------+
Example Walkthrough
For the classes above:
- Base: has a vtable with one entry pointing to
Base::greet
. - Derived: has a vtable with one entry overridden to point to
Derived::greet
. - An object of
Derived
contains avptr
pointing toDerived
's vtable.
At runtime, when b->greet()
is called and b
points to a Derived
object, the program:
- Uses
b
'svptr
to getDerived
's vtable. - Looks up the function pointer for
greet()
in the vtable. - Calls
Derived::greet()
.
Important Notes
- Vtables are an implementation detail — the C++ standard does not require compilers to use them, but all mainstream compilers do.
- Each polymorphic class usually has its own unique vtable.
- Each object of such a class has a hidden
vptr
pointer added by the compiler, which you don't see in your C++ code. - Multiple inheritance and virtual inheritance introduce more complex vtable structures.
Summary
The vtable is the core mechanism behind C++'s support for runtime polymorphism. It allows virtual functions to behave correctly according to the actual object type, enabling powerful and flexible object-oriented designs.
Understanding vtables can help you better grasp how C++ works under the hood and why certain features behave the way they do.