C++ VTable | "가상 함수 테이블" 가이드
이 글의 핵심
C++ VTable에 대해 정리한 개발 블로그 글입니다. class Base { public: virtual void func() {} };
VTable이란?
가상 함수 포인터를 저장하는 테이블
class Base {
public:
virtual void func() {}
};
// 메모리 구조:
// [vptr] -> VTable -> [func 주소]
작동 원리
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
실전 예시
예시 1: 메모리 구조
#include <iostream>
class Base {
public:
virtual void func1() {}
virtual void func2() {}
int data = 10;
};
int main() {
Base b;
std::cout << "크기: " << sizeof(b) << std::endl;
// 8 (vptr) + 4 (data) + padding
}
예시 2: 다형성
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;
}
}
예시 3: vptr 확인
class Base {
public:
virtual void func() {}
};
class Derived : public Base {
public:
void func() override {}
};
int main() {
Base b;
Derived d;
// vptr 위치 (첫 8바이트)
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;
}
예시 4: 성능 비교
class NonVirtual {
public:
void func() {}
};
class Virtual {
public:
virtual void func() {}
};
// 호출 비교
NonVirtual nv;
nv.func(); // 직접 호출
Virtual v;
v.func(); // vptr -> vtable -> 함수 (간접 호출)
자주 발생하는 문제
문제 1: 가상 소멸자 누락
// ❌ 가상 소멸자 없음
class Base {
public:
~Base() {} // 비가상
};
class Derived : public Base {
int* data;
public:
~Derived() { delete[] data; }
};
Base* b = new Derived();
delete b; // Derived 소멸자 호출 안됨
// ✅ 가상 소멸자
class Base {
public:
virtual ~Base() {}
};
문제 2: 생성자에서 가상 함수
class Base {
public:
Base() {
init(); // Base::init 호출 (다형성 안됨)
}
virtual void init() {
std::cout << "Base init" << std::endl;
}
};
class Derived : public Base {
public:
void init() override {
std::cout << "Derived init" << std::endl;
}
};
문제 3: 성능 오버헤드
// 가상 함수 호출은 간접 호출
for (int i = 0; i < 1000000; i++) {
obj->virtualFunc(); // vtable 조회
}
// 최적화: final 사용
class Derived final : public Base {
void func() override final {}
};
문제 4: 메모리 크기
class NoVirtual {
int x;
}; // sizeof = 4
class WithVirtual {
int x;
virtual void func() {}
}; // sizeof = 16 (vptr 포함)
VTable 최적화
// 1. final 키워드
class Base {
virtual void func() {}
};
class Derived final : public Base {
void func() override final {}
};
// 2. 비가상 인터페이스
class Base {
public:
void func() { // 비가상
funcImpl();
}
private:
virtual void funcImpl() {}
};
FAQ
Q1: VTable은 언제?
A: 가상 함수 있는 클래스.
Q2: 성능 영향?
A:
- 간접 호출
- 메모리 증가 (vptr)
- 캐시 미스 가능
Q3: 가상 소멸자 필수?
A: 다형성 사용 시 필수.
Q4: 최적화는?
A:
- final 키워드
- 비가상 인터페이스
- 템플릿
Q5: VTable 크기는?
A: 가상 함수 개수 × 포인터 크기.
Q6: VTable 학습 리소스는?
A:
- “Inside the C++ Object Model”
- “Effective C++”
- “C++ Internals”
같이 보면 좋은 글 (내부 링크)
이 주제와 연결되는 다른 글입니다.
- C++ 가상 함수(Virtual Function)와 vtable의 동작 원리 [#33-1]
- C++ 가상 함수 | “Virtual Functions” 가이드
- C++ CRTP 완벽 가이드 | 정적 다형성과 컴파일 타임 최적화
관련 글
- C++ vtable 에러 |