포스트

가상 함수(virtual function)와 가상 함수 테이블(vtable)

가상 함수(virtual function)란

C++ 프로그래밍 언어는 객체 지향 프로그래밍(OOP)을 지원하며, 그 중 다형성(polymorphism)이 중요한 개념 중 하나입니다.

다형성을 구현하는 한 방법으로, 가상 함수는 유연성확장성을 크게 향상시킵니다.

가상 함수(virtual function)는 기본 클래스에서 선언되고, 파생 클래스에서 재정의할 수 있는 멤버 함수입니다.

이를 통해, 파생 클래스의 객체를 가리키는 기본 클래스의 포인터를 사용하여 재정의된 함수를 호출할 수 있습니다.

가상 함수의 주된 이점은 실행 시간에 결정되는 함수 호출을 통해, 소프트웨어의 다양한 컴포넌트 간에 더 유연한 상호 작용을 가능하게 하는 것입니다.


가상 함수는 virtual 키워드를 사용하여 선언하며, C++11 이후 버전에서는 override 키워드를 사용하여 재정의함으로써, 오버라이딩의 정확성을 컴파일러가 검증할 수 있게 합니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
class Base
{
public:
    virtual void display()
    {
        cout << "Display Base" << endl;
    }
};

class Derived : public Base
{
public:
    void display() override // 파생 클래스에서는 override 키워드를 사용하고, virtual 키워드 생략 가능
    {
        cout << "Display Derived" << endl;
    }
};

가상 함수(virtual function)의 동작 원리와 가상 테이블(vtable)

C++의 가상 함수는 런타임 다형성을 구현하기 위해 가상 함수 테이블(vtable)이라는 구조를 사용합니다.

가상 함수 테이블은 각 클래스에 대한 가상 함수 포인터들을 저장하는 배열입니다.

클래스의 각 인스턴스는 가상 함수 테이블을 가리키는 포인터(vptr)를 가지고 있으며, 이를 통해 런타임에 어떤 함수를 호출할지 결정합니다.

  1. 클래스 정의 시 vtable 생성
    • 가상 함수가 하나라도 존재하는 클래스에 대해 컴파일러가 가상 함수 테이블을 생성합니다.
    • 이 테이블에는 해당 클래스의 각 가상 함수에 대한 포인터가 저장됩니다.
  2. vptr 초기화
    • 인스턴스가 생성될 때, vptr은 해당 클래스의 vtable을 가리키도록 초기화됩니다.
  3. 가상 함수 호출
    • 가상 함수 호출 시, vptr을 통해 적절한 vtable을 찾아가고, 그 안의 함수 포인터를 사용하여 함수를 호출합니다.

가상 함수 테이블(vtable)의 구조

가상 함수 테이블은 가상 함수를 포함하는 모든 클래스에 대해 생성되며, 파생 클래스에서 가상 함수를 재정의하면, 해당 함수의 포인터가 새롭게 정의된 함수를 가리키도록 업데이트됩니다.

즉, 다음 코드와 같이 가상 함수를 만들었다면 이미지와 같은 가상 함수 테이블이 생성됩니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
class Base
{
public:
    virtual void VirtualFunc()
    {
        cout << "Base::VirtualFunc" << endl;
    }
    virtual void VirtualFunc2()
    {
        cout << "Base::VirtualFunc2" << endl;
    }
    virtual ~Base() {}
};

class Derived : public Base
{
public:
    virtual void VirtualFunc() override
    {
        cout << "Derived::VirtualFunc" << endl;
    }
    virtual ~Derived() {}
};

이때, DerivedVirtualFunc2를 재정의 하지 않았으므로, 가상 함수 포인터를 업데이트 하지 않고 Base::VirtualFunc2 포인터를 그대로 가지고 있습니다.

즉, 재정의되지 않은 함수는 기본 클래스의 포인터를 유지하게 됩니다.

vtable 구조

성능

가상 함수와 가상 함수 테이블의 사용은 편의성유연성을 제공하지만, 이에는 약간의 성능 오버헤드가 따릅니다.

가상 함수를 호출할 때마다 vptr을 통해 vtable을 참조해야 하므로, 직접 함수 호출보다 느릴 수 있습니다.

따라서, 가상 함수는 주로 런타임 다형성이 필요한 경우에만 사용하고, 불필요한 경우에는 피하는 것이 좋습니다.

이 기사는 저작권자의 CC BY-NC-ND 4.0 라이센스를 따릅니다.