C++ VTable Explained: Virtual Function Tables & Dynamic Dispatch
이 글의 핵심
Overview of the C++ vtable: virtual function pointers, vptr layout, why the first virtual call is indirect, and when CRTP avoids virtual cost.
What is a VTable?
A table that stores pointers to virtual functions.
class Base {
public:
virtual void func() {}
};
// Memory layout:
// [vptr] -> VTable -> [address of func]
How it works
class Animal {
public:
virtual void speak() {
std::cout << "Animal" << std::endl;
}
};
class Dog : public Animal {
public:
void speak() override {
std::cout << "Woof!" << std::endl;
}
};
// VTable:
// Animal: [speak -> Animal::speak]
// Dog: [speak -> Dog::speak]
Animal* a = new Dog();
a->speak(); // vptr -> Dog vtable -> Dog::speak
Practical examples
Example 1: Memory layout
#include <iostream>
class Base {
public:
virtual void func1() {}
virtual void func2() {}
int data = 10;
};
int main() {
Base b;
std::cout << "size: " << sizeof(b) << std::endl;
// 8 (vptr) + 4 (data) + padding
}
Example 2: Polymorphism
class Shape {
public:
virtual void draw() = 0;
virtual double area() = 0;
virtual ~Shape() = default;
};
class Circle : public Shape {
double radius;
public:
Circle(double r) : radius(r) {}
void draw() override {
std::cout << "Circle" << std::endl;
}
double area() override {
return 3.14 * radius * radius;
}
};
class Rectangle : public Shape {
double width, height;
public:
Rectangle(double w, double h) : width(w), height(h) {}
void draw() override {
std::cout << "Rectangle" << std::endl;
}
double area() override {
return width * height;
}
};
int main() {
std::vector<std::unique_ptr<Shape>> shapes;
shapes.push_back(std::make_unique<Circle>(5));
shapes.push_back(std::make_unique<Rectangle>(4, 6));
for (auto& shape : shapes) {
shape->draw();
std::cout << "Area: " << shape->area() << std::endl;
}
}
Example 3: Inspecting vptr
class Base {
public:
virtual void func() {}
};
class Derived : public Base {
public:
void func() override {}
};
int main() {
Base b;
Derived d;
// vptr location (often first 8 bytes)
void** vptr_b = *(void***)&b;
void** vptr_d = *(void***)&d;
std::cout << "Base vptr: " << vptr_b << std::endl;
std::cout << "Derived vptr: " << vptr_d << std::endl;
}
Example 4: Performance comparison
class NonVirtual {
public:
void func() {}
};
class Virtual {
public:
virtual void func() {}
};
// Call comparison
NonVirtual nv;
nv.func(); // direct call
Virtual v;
v.func(); // vptr -> vtable -> func (indirect)
Common pitfalls
Pitfall 1: Missing virtual destructor
// Bad: non-virtual destructor
class Base {
public:
~Base() {} // non-virtual
};
class Derived : public Base {
int* data;
public:
~Derived() { delete[] data; }
};
Base* b = new Derived();
delete b; // Derived destructor not called
// Good: virtual destructor
class Base {
public:
virtual ~Base() {}
};
Pitfall 2: Virtual calls from constructors
class Base {
public:
Base() {
init(); // calls Base::init (no polymorphism in ctor)
}
virtual void init() {
std::cout << "Base init" << std::endl;
}
};
class Derived : public Base {
public:
void init() override {
std::cout << "Derived init" << std::endl;
}
};
Pitfall 3: Performance overhead
// Virtual calls are indirect
for (int i = 0; i < 1000000; i++) {
obj->virtualFunc(); // vtable lookup
}
// Optimization: final
class Derived final : public Base {
void func() override final {}
};
Pitfall 4: Object size
class NoVirtual {
int x;
}; // sizeof may be 4
class WithVirtual {
int x;
virtual void func() {}
}; // sizeof includes vptr
VTable-oriented optimizations
// 1. final keyword
class Base {
virtual void func() {}
};
class Derived final : public Base {
void func() override final {}
};
// 2. Non-virtual interface (NVI)
class Base {
public:
void func() { // non-virtual
funcImpl();
}
private:
virtual void funcImpl() {}
};
FAQ
Q1: When do you get a vtable?
A: For classes that have virtual functions.
Q2: Performance impact?
A:
- Indirect calls
- Extra memory (vptr)
- Possible cache misses
Q3: Is a virtual destructor required?
A: When you delete through a base pointer to a derived object—yes.
Q4: How to optimize?
A:
final- Non-virtual interface idiom
- Templates (e.g. CRTP)
Q5: How big is a vtable?
A: Roughly number of virtual functions × pointer size.
Q6: Learning resources?
A:
- Inside the C++ Object Model
- Effective C++
- C++ internals references
Related posts (internal links)
- Virtual functions and vtable mechanics (series)
- C++ Virtual Functions
- C++ CRTP: static polymorphism
Practical tips
Tips you can apply immediately.
Debugging
- Enable and read compiler warnings first
- Reproduce with a small test case
Performance
- Do not optimize without profiling
- Define measurable targets first
Code review
- Check common review feedback early
- Follow team conventions
Practical checklist
Before coding
- Is this the best fit for the problem?
- Can teammates maintain it?
- Does it meet performance needs?
While coding
- Are warnings cleared?
- Edge cases covered?
- Error handling appropriate?
At review
- Is intent clear?
- Tests sufficient?
- Documentation adequate?
Use this checklist to reduce mistakes and improve quality.
Keywords
C++, vtable, virtual function, polymorphism, dynamic dispatch
Related posts
- C++ vtable linker error