[2026] C++ VTable Explained: Virtual Function Tables & Dynamic Dispatch
이 글의 핵심
How C++ vtables and vptrs implement polymorphism: indirect calls, object size, multiple inheritance costs, and optimization with final and NVI.
What is a VTable?
A table that stores pointers to virtual functions. 아래 코드는 cpp를 사용한 구현 예제입니다. 클래스를 정의하여 데이터와 기능을 캡슐화하며. 코드를 직접 실행해보면서 동작을 확인해보세요.
class Base {
public:
virtual void func() {}
};
// Memory layout:
// [vptr] -> VTable -> [address of func]
How it works
다음은 cpp를 활용한 상세한 구현 코드입니다. 클래스를 정의하여 데이터와 기능을 캡슐화하며. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.
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
아래 코드는 cpp를 사용한 구현 예제입니다. 필요한 모듈을 import하고, 클래스를 정의하여 데이터와 기능을 캡슐화하며. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.
#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
다음은 cpp를 활용한 상세한 구현 코드입니다. 클래스를 정의하여 데이터와 기능을 캡슐화하며, 반복문으로 데이터를 처리합니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.
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
다음은 cpp를 활용한 상세한 구현 코드입니다. 클래스를 정의하여 데이터와 기능을 캡슐화하며. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.
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
다음은 cpp를 활용한 상세한 구현 코드입니다. 클래스를 정의하여 데이터와 기능을 캡슐화하며. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.
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
다음은 cpp를 활용한 상세한 구현 코드입니다. 클래스를 정의하여 데이터와 기능을 캡슐화하며. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.
// 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
다음은 cpp를 활용한 상세한 구현 코드입니다. 클래스를 정의하여 데이터와 기능을 캡슐화하며. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.
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
아래 코드는 cpp를 사용한 구현 예제입니다. 클래스를 정의하여 데이터와 기능을 캡슐화하며, 반복문으로 데이터를 처리합니다. 코드를 직접 실행해보면서 동작을 확인해보세요.
// 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
아래 코드는 cpp를 사용한 구현 예제입니다. 클래스를 정의하여 데이터와 기능을 캡슐화하며. 코드를 직접 실행해보면서 동작을 확인해보세요.
class NoVirtual {
int x;
}; // sizeof may be 4
class WithVirtual {
int x;
virtual void func() {}
}; // sizeof includes vptr
VTable-oriented optimizations
다음은 cpp를 활용한 상세한 구현 코드입니다. 클래스를 정의하여 데이터와 기능을 캡슐화하며. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.
// 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)
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.