Post

Introduction to Virtual Tables in C++

Photo by Nataliya Smirnova on Unsplash

What is a vtable?

  • vtable: A vtable is essentially C++’s implementation of polymorphism. A vtable contains an array of pointers to virtual functions. Each class that has virtual functions (or inherits from a class with virtual functions) has its own vtable.
  • vptr: Each object of such a class contains a pointer (usually referred to as vptr) to the vtable of its class. This pointer allows the object to access the correct function implementations.

How it works:

  1. Class Definition:
  • When a class with virtual functions is defined, the compiler generates a vtable for that class. The vtable contains pointers to the virtual function implementations.
  1. Object Instantiation:
  • When an object of that class (or a derived class) is instantiated, the compiler sets the vptr of the object to point to the vtable of the appropriate class.
  1. Function Call:
  • When a virtual function is called on an object, the call is resolved at runtime by looking up the function pointer in the vtable via the vptr. This allows the program to call the correct function even if the call is made through a base class pointer.

Example:

#include <iostream>
class Base {
public:
virtual void show() {
std::cout << "Base show" << std::endl;
}
virtual void print() {
std::cout << "Base print" << std::endl;
}
};
class Derived : public Base {
public:
void show() override {
std::cout << "Derived show" << std::endl;
}
void print() override {
std::cout << "Derived print" << std::endl;
}
};
int main() {
Base* b = new Derived();
b->show(); // Outputs: "Derived show"
b->print(); // Outputs: "Derived print"
delete b;
return 0;
}

What Happens Under the Hood:

  1. Vtable Creation:
  • The compiler creates a vtable for Base containing pointers to Base::show and Base::print.
  • It also creates a vtable for Derived containing pointers to Derived::show and Derived::print.

2. Object Initialization:

  • When Derived object is created, its vptr is set to point to the Derived's vtable.

3. Virtual Function Call:

  • When b->show() is called, the program follows b's vptr to the Derived's vtable and calls Derived::show.
  • Similarly, b->print() calls Derived::print.

Visual Representation:

Base vtable:           Derived vtable:
+-----------+ +-----------+
| Base::show| | Derived::show|
| Base::print| | Derived::print|
+-----------+ +-----------+
Derived object:
+-----------+
| vptr ----> | --------> Derived vtable
+-----------+

Performance Considerations:

  • Overhead: The primary overhead involves an extra level of indirection (the vtable lookup) for each virtual function call, and the storage for the vtable and the vptr in each object.
  • Impact: In most applications, this overhead is negligible.

Why are vtables Necessary?

For classes in C++, non-virtual functions do not have a vtable because they are resolved at compile time, not at runtime. To better understand this, we need to understand the differences between static vs dynamic binding.

Static Binding vs. Dynamic Binding

Static Binding:

  • Non-virtual functions use static binding, meaning that the function to be called is determined at compile time based on the type of the pointer or reference.
  • The compiler knows the exact function to call, so it generates a direct call to the function’s address.
  • No extra indirection or lookup is needed.

Dynamic Binding:

  • Virtual functions use dynamic binding, meaning that the function to be called is determined at runtime based on the actual object type.
  • This requires an additional level of indirection through the vtable to resolve the function address.

I hope you understand the use of vtables now! Thank you!

This post is licensed under CC BY 4.0 by the author.