본문 바로가기

About Programing/08. C++

가상함수와 테이블 (vtbl)

 출처 : http://skmagic.tistory.com/140 / http://zerobell.tistory.com/17

1. 가상함수


 동적 결합을 하는 함수.
 가상함수로 선언하면 포인터의 타입이 아닌 포인터가 가리키는 객체의 타입에 따라 멤버 함수를 선택한다.

virtual 키워드를 선언부에 넣어주면 가상함수가 된다.

#include <iostream>

using namespace std;

class CCellPhone
{
public :
 virtual void CellPhone_1 ();
 void CellPhone_2 ();
};

class CGalaxyS : public CCellPhone
{
public :
 virtual void CellPhone_1 ();
 void CellPhone_2 ();
};

void CCellPhone::CellPhone_1()
{
 cout<<"My phone is cell phone!"<<endl;
}

void CCellPhone::CellPhone_2()
{
 cout<<"My phone is cell phone!"<<endl;
}

void CGalaxyS::CellPhone_1 ()
{
 cout<<"My phone is cell GalaxyS!"<<endl;
}

void CGalaxyS::CellPhone_2 ()
{
 cout<<"My phone is cell GalaxyS!"<<endl;
}

int main (void)
{
 CCellPhone* pGalaxyS = new CGalaxyS();
 CCellPhone* pCellPhone = new CCellPhone ();

 pGalaxyS->CellPhone_1();
 pCellPhone->CellPhone_1();

 pGalaxyS->CellPhone_2();
 pCellPhone->CellPhone_2();

 return 0;
}

글을 읽으면서 위 소스의 출력값에 대해서 생각해 보자.

Binding(바인딩) : 함수 호출문에 대해 실제 호출될 함수의 번지를 결정하는 것.
정적 바인딩 : 함수 호출문에 대한 번지가 컴파일시 이미 정해져 있는 것.
동적 바인딩 : 함수 호출문에 대한 번지가 컴파일 시가 아닌 실행 중에 호출할 함수를 결정하는 것.
이것은 가상함수 테이블을 이용한다. 동적 바인딩은 포인터(또는 레퍼런스)로 호출할 때만 동작함.
포인터가 아니면 어떤 객체에 대해서 함수인지 알수 있으므로 적적바인딩 됢. 당연한 소리.

2. 가상함수 테이블 (vtable)

 가상함수를 가지는 클래스는 가상함수 테이블을 가지고 있고,
클래스 안에서는 이 테이블을 가리킬수 있는 포인터를 가지고 있다.
이 때문에 가상함수를 가지는 클래스는 크기가 4byte 추가 할당 된다.
가상함수 테이블을 가상함수들만 들어가고, 가상함수 주소들의 배열 형태로 존재한다.

 당연한 이야기지만, 한 클래스에서 많은 가상함수를 가지고 있더라도 같은 vtable을 사용한다.
가상함수 테이블을 객체마다 가지는 것이 아니고,
클래스 별로 가지고 있는 것이다.

3. 가상함수를 사용하는 이유

 부모클래스형 포인터로 멤버 함수를 호출할 때, 컴파일러는 정적 타입을 보고,
이 타입에 맞는 멤버 함수를 호출한다.(비가상 함수)
그래서 정적타입이 아닌 동적타입에 따른 함수를 부르기 위해 가상함수를 사용한다.(동적 바인딩)

- 비가상 함수 : 포인터가 어떤 객체를 가리키는가에 상관없이 항상 포인터 타입 클래스의 멤버 함수 호출.
- 가상 함수 : 포인터가 가리키는 실제 객체의 함수를 호출.

이제 위 소스의 출력값에 대해서 유추 할 수 있을 것이다.


그래서!
재정의 할 가능성이 있는 멤버 함수는 가상함수로 선언하는 것이 좋다.

보통은 재사용가능성이 많은 소멸자를 가상함수로 만들어 준다.
상속 관계에서 자식 클래스의 소멸자는 반드시 가상으로 선언해야 한다.