Unlocking C++ Polymorphism: How Virtual Tables Enable Runtime Flexibility
This article explains how C++ implements polymorphism through virtual functions and vtables, covering static and dynamic polymorphism, memory layout, multiple inheritance, practical code examples, design‑pattern applications, and the importance of virtual destructors for safe resource cleanup.
When you write Base* ptr = new Derived(); ptr->func(); you may wonder how the compiler knows to call Derived::func() instead of Base::func(). The answer lies in a carefully designed runtime mechanism that lets the program discover an object's true type and invoke the correct function, which is the essence of C++ polymorphism.
1. What Is Polymorphism?
1.1 Overview of C++ Polymorphism
Polymorphism means that the same operation can exhibit different behaviors for different objects, similar to how driving a race car feels different from driving a family car. In C++, polymorphism is mainly achieved through virtual functions. When a base‑class pointer or reference refers to a derived‑class object, calling a virtual function results in behavior specific to the derived class.
C++ distinguishes between static (compile‑time) polymorphism, implemented via function overloading and templates, and dynamic (run‑time) polymorphism, implemented via virtual functions.
1.2 How Polymorphism Solves Code‑Reuse Problems
Without polymorphism, adding new shapes such as a trapezoid would require writing new drawing functions and updating many call sites. By introducing a common abstract base class Shape with a virtual draw() method, each concrete shape overrides this method, and client code can operate on an array of Shape* without modification.
1.3 Polymorphism Makes Extension Easier
In game development, different character types (warrior, mage, assassin) each have unique attack and movement logic. Using a base class Character with virtual attack() and move() methods allows new character types to be added simply by deriving a new class and overriding the methods, without changing existing game logic.
1.4 Implementation Principles of Polymorphism
Dynamic polymorphism in C++ relies on virtual function tables (vtables) and a hidden pointer (vptr) inside each object that points to the appropriate vtable. When a virtual function is called, the program looks up the function address in the vtable at run time.
2. What Is a Virtual Function Table?
2.1 Definition
A vtable is a compiler‑generated array that stores the addresses of a class's virtual functions. It acts as a function‑address directory, enabling the runtime to locate and invoke the correct function implementation.
2.2 Why a Vtable Is Needed
Without a vtable, a base‑class pointer could only call the base‑class version of a function, making true runtime polymorphism impossible. The vtable allows the program to resolve the correct overridden function based on the object's actual type.
2.3 Memory Layout
Each object of a class that contains virtual functions has a hidden vptr, typically placed at the beginning of the object's memory layout. The vtable itself resides in the read‑only data segment (.rodata) because its contents do not change at runtime.
2.4 Vtable Changes in Inheritance
In single inheritance without overrides, the derived class's vtable contains the base class's entries followed by the derived class's new virtual functions. When a derived class overrides a base virtual function, the corresponding entry in the vtable is replaced with the derived implementation. Multiple inheritance creates separate vtables for each base subobject, and the derived object contains multiple vptrs.
3. Practical Cases
3.1 Polymorphism in Design Patterns
Strategy Pattern : Define an abstract OperationStrategy interface with a virtual execute() method. Concrete strategies (Add, Subtract, Multiply, Divide) implement this method. A Calculator holds a pointer to OperationStrategy and delegates the calculation, allowing the algorithm to be changed at runtime without modifying the calculator.
Factory Method Pattern : Define an abstract Character base class with a virtual display() method. Concrete character classes (Warrior, Mage, Assassin) override display(). An abstract CharacterFactory declares a pure virtual createCharacter(). Concrete factories instantiate the appropriate character, enabling client code to create objects without knowing their concrete types.
3.2 Accessing the Vtable Directly (Advanced)
Although not common in production code, you can obtain an object's vptr by reinterpret‑casting the object address to an integer pointer, then dereferencing it to get the vtable address. Function pointers can be extracted from the vtable and invoked directly, illustrating how the runtime resolves virtual calls.
3.3 Vtable in Real‑World Polymorphic Systems
In a graphics system, an abstract Shape class defines a pure virtual Draw() method. Concrete shapes (Circle, Rectangle, Triangle) override Draw(). A function DrawShapes(const Shape* shapes[], int count) iterates over an array of Shape* and calls Draw(), relying on the vtable to dispatch the correct implementation for each shape.
4. Virtual Destructors (Preventing Memory Leaks)
If a base‑class pointer deletes a derived object and the base destructor is not virtual, only the base destructor runs, potentially leaking resources allocated by the derived class. Declaring the base destructor as virtual ensures that the derived destructor is invoked first, followed by the base destructor, guaranteeing proper cleanup.
#include <iostream>
using namespace std;
class CShape {
public:
virtual ~CShape() { cout << "CShape::destructor" << endl; }
};
class CRectangle : public CShape {
public:
int w, h;
~CRectangle() { cout << "CRectangle::destructor" << endl; }
};
int main() {
CShape* p = new CRectangle;
delete p; // Calls both destructors because ~CShape is virtual
return 0;
}Generally, any class that defines virtual functions should also have a virtual destructor; constructors cannot be virtual.
Deepin Linux
Research areas: Windows & Linux platforms, C/C++ backend development, embedded systems and Linux kernel, etc.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.
