단일 상속 구조에서 가상 함수 테이블의 구성

C++의 객체 지향 프로그래밍에서 가상 함수는 다형성을 구현하는 핵심 요소입니다. 이전 글에서는 단일 클래스의 가상 함수 테이블 구조를 살펴보았고, 이번에는 일반적인 단일 상속 관계에서 가상 함수 테이블이 어떻게 구성되는지 분석해보겠습니다.

예제 코드

#include <iostream>
#include <typeinfo>

class BaseClass {
public:
    BaseClass() {}
    virtual ~BaseClass() {}
    virtual void display() {
        std::cout << "BaseClass::display\n";
    }
    virtual void process() = 0;
private:
    int base_value = 100;
};

class ChildClass : public BaseClass {
public:
    ChildClass() {}
    ~ChildClass() {}
    virtual void execute() {
        std::cout << "ChildClass::execute\n";
    }
    void process() override {
        std::cout << "value: " << child_value << '\n';
    }
private:
    int child_value = 200;
};

int main() {
    std::cout << sizeof(ChildClass) << '\n';
    BaseClass *ptr = new ChildClass;
    const std::type_info &info = typeid(*ptr);
    std::cout << info.name() << '\n';
    delete ptr;
    return 0;
}

기본 클래스의 가상 함수 테이블 구조

순수 가상 함수를 포함한 클래스는 인스턴스화할 수 없지만, 가상 함수 테이블을 분석하기 위해 Compiler Explorer 또는 GDB와 같은 도구를 사용할 수 있습니다.

  1. 순수 가상 함수가 포함된 클래스는 인스턴스화가 불가능하므로 소멸자 항목도 유효한 주소를 가지지 않습니다.
  2. 가상 함수의 순서는 클래스 내 선언 순서와 동일합니다. 생성자 다음에 display(), 마지막으로 process() 순으로 배치됩니다.
  3. __cxa_pure_virtual은 순수 가상 함수 호출 시 실행되는 에러 처리 함수로, 결국 std::abort()를 호출합니다.

파생 클래스의 가상 함수 테이블 구조

단일 상속 구조에서는 기본 클래스와 파생 클래스가 동일한 가상 함수 테이블을 공유합니다.

통합된 가상 함수 테이블

단일 비가상 공개 상속의 경우, 기본 클래스 하위 객체와 파생 클래스는 하나의 가상 함수 테이블을 공유합니다. 파생 클래스에서 재정의된 함수는 파생 클래스 버전으로 대체되고, 새로 추가된 가상 함수는 테이블의 끝에 선언 순서대로 추가됩니다.

__si_class_type_info의 역할

이 구조에서는 타입 정보 포인터가 __si_class_type_info 객체를 가리킵니다. 이 클래스는 이전 글에서 언급한 __class_type_info를 상속받으며, 단일 공개 비가상 상속 조건을 만족하는 클래스에 사용됩니다.

예외 처리 시 타입 매칭

try {
    throw ChildClass();
} catch (const BaseClass& obj) {
    obj.process();
}

예외 발생 시 __do_catch 함수에서 타입 매칭을 수행하며, __base_type 포인터를 통해 상위 클래스와의 호환성을 검사합니다.

dynamic_cast 연산에서의 타입 추적

struct A { virtual ~A(){} };
struct B : public A {};
struct C : public B {};
struct D : public C {};

int main() {
    A *pa = new D;
    B *pb = dynamic_cast<B*>(pa);
    int result = (nullptr == pb) ? -1 : 0;
    delete pa;
    return result;
}

다운캐스트 시 __dynamic_cast 함수는 가장 파생된 객체의 타입 정보를 기반으로 목표 타입까지의 경로를 탐색합니다.

공개 기반 클래스 탐색

변환 대상 주소를 찾은 후 해당 타입이 공개 기반 클래스인지 확인하기 위해 __find_public_src 함수를 통해 상위 계층을 탐색합니다.

__class_type_info::__sub_kind __si_class_type_info::
__do_find_public_src(ptrdiff_t offset,
                     const void *obj_ptr,
                     const __class_type_info *src_type,
                     const void *src_ptr) const {
    if (src_ptr == obj_ptr && *this == *src_type)
        return __contained_public;
    return __base_type->__do_find_public_src(offset, obj_ptr, src_type, src_ptr);
}

요약

  • 순수 가상 함수는 __cxa_pure_virtual 함수와 연결되어 호출 시 프로그램을 종료시킵니다.
  • 단일 공개 비가상 상속 구조에서는 기본 클래스와 파생 클래스가 가상 함수 테이블을 공유하며, 재정의된 함수와 새 함수는 적절한 위치에 배치됩니다.
  • __si_class_type_info는 기본 클래스 타입 정보에 대한 포인터를 포함하여 예외 처리 및 타입 변환 시 상위 클래스로의 탐색을 가능하게 합니다.

태그: C++ virtual function table vtable typeinfo dynamic_cast

8월 13일 11:28에 게시됨