본문 바로가기

3.구현/C or C++

가상함수 구조

C++를 하다보면, 가상함수 호출하는 방식이 궁금했었다. 어떻게 다른 클래스로 캐스팅해서 원하는 가상함수를 호출하는지 정말 신기했다. 그래서 찾아봤다.

작성: http://ospace.tistory.com/(ospace114@empal.com),2010.12.07

사용할 예제코드

다음은 단순한 상속예제이다. Base 클래스에는 두 개의 가상함수가 정의되어 있고, Derived 클래스에 한개의 가상 함수를 구현하고 있다.

class Base {
private:
    int valBase;
public:
    virtual int getValue() {
        return this->valBase;
    }
    virtual char* getName() {
        return "Base";
    }
};
class Derived : public Base {
private:
    int valDerived;
public:
    virtual int getValue() {
        return this->valDerived;
    }
};

가상테이블

가상함수를 하다보면, 반드시 나오는 것이 가상 함수 테이블이다. 이는 내부적으로 가상함수 호출를 관리하기 위한 테이블이다. 위의 예제에 대해서 실행상태에서 가상 함수 테이블의 구조를 살펴보면 다음과 같다.

Base나 Derived 인스탄스 맨 처음에는 가상함수 테이블에 대한 포인터(vptr)이 온다. 즉, 가상 함수 테이블은 별도로 관리되면, 인스탄스에는 해당 테이블에 대한 포인터만 관리한다. 그리고, 가상 함수 테이블에는 실제 함수가 구현된 포인터가 저장된다.

한가지 재미 있는 것은 자식 클래스가 가상함수에 대한 구현을 하지 않으면, 가상 함수 테이블에서는 자동으로 부모 클래스의 가상함수가 구현된 포인터로 연결이 된다.

추가로 매번 인스탄스될 때마다 가상 함수 테이블이 생성이 되면, 가상함수가 매우 많은 클래스인 경우 메모리 낭비가 심해진다. 그래서, 해당 클래스에 대한 가상 함수 테이블은 모든 인스탄스와 공유하여 사용하게 된다.

호출하는 순서는 간단하다. 상황은 Derived 인스탄스에서 getValue()을 호출한다고 하자.

  1. Derived 인스탄스에서 가상 함수 테이블 포인터(vptr)을 획득
  2. 획득한 가상함수 테이블에서 getValue() 포인터 획득
  3. 획득한 getValue()를 호출

추가로

몇가지 추가로 생각해볼만한 부분이 있다.

  • 순수 가상함수나, 구현이 없는 가상함수인 경우 함수 구현부는 어떻게 될까?
  • 다중 상속에서 양쪽 부모에서 같은 이름을 갖는 가상 함수에 대한 호출은 어떻게 될까?
  • 다중 상속에서 가상 함수 테이블 여러개가 존재하는데, 언제 어떤 테이블을 사용할까?
  • 다중 상속에서 각 함수 테이블에서 가상 함수 호출할 때에 this 포인터 위치는 어떻게 될까?

이 부분은 여러분에게 맡긴다. 아직 정리가 안되어서 올리지는 못했다. 조만간 빨리 정리되는데로 올리겠지만, 언제 올릴지는 모르겠다.

참조

[1] 가상함수테이블, http://winapi.co.kr/clec/cpp3/30-1-4.png

반응형