C++ VTable | "가상 함수 테이블" 가이드

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 에러 |