가상함수 테이블(vtbl) 과 가상함수 테이블 포인터(vptr)
- 대부분의 컴파일러는 가상함수의 기능을 구현하기 위해 가상함수 테이블과 가상함수 테이블
포인터를 사용한다. 가상함수를 선언했거나 상속받은 클래스에는 가상함수의 모든 주소를 가
지고 있는 배열(혹은 링크드 리스트)이 생기는데 이를 가상함수 테이블 이라고 한다.
또한, 해당 클래스의 객체를 생성하면 해당 객체에는 멤버 data외에 가상 함수 테이블을
가르키는 포인터가 추가된다. 이를 가상함수 테이블 포인터라고 한다.
결국 가상함수를 사용하게 되면 실행시간 다형성(Run-time Polymorphism)을 얻을 수 있는 반면,
1. 가상함수 테이블을 위한 메모리 할당
( 가상함수가 많거나 상속이 깊을 경우 메모리 사용량이 많아진다. )
2. 객체 생성시 vptr의 추가로 인한 객체의 크기 증가
( 작은 객체 일 경우 상당히 비효율적 )
3. 함수 호출시 함수 포인터에 의한 호출로 약간의 오버헤드 발생
4. Inline을 사용할 수 없으므로 속도가 훨씬 느려진다.
class A
{
public:
virtual void foo() { cout << "A::foo" << endl; } // 1
};
class B
{
public:
virtual void goo() { cout << "B:goo" << endl; } // 2
};
void main()
{
A a;
B* p = (B*)&a;
p->goo(); // 1을 출력한다.
}
// 예제 2.
// 가상함수의 테이블을 이해했다면 이건 메모리 참조에러가 나온다.
class A
{
int x;
public:
void foo() { cout << "A::foo" << endl; }
};
class B
{
int y;
public:
virtual void goo() { cout << "B:goo" << endl; }
};
void main()
{
A a;
B* p = (B*)&a;
p->goo(); // error
}
// 예제 3.
// p->foo() 에서 디폴트 값은 컴파일 시간에 결정된다.!!!
class A
{
public:
virtual void foo( int a = 10 )
{ cout << "A::foo("<< a << ")" << endl; } // 1
};
class B : public A
{
public:
virtual void foo( int a = 20 )
{ cout << "B:foo("<< a << ")" << endl; } // 2
};
void main()
{
A* p = new B;
p->foo(); // 디폴트 매개변수는 부모에게서 받아오고 출력은 2로 한다.
delete p;
}
Dynamic Binding -> 실행 시 결정된다.
= late binding
Static Binding -> 컴파일러가 컴파일시 결정.
= early binding
// Static_Binding 자기를 호출하는 놈을 부른다. 메모리가 아니라 타입을 읽어온다.
컴파일 타임에는 타입을 보므로 디폴트타입을 정할떄에는 컴파일시 실행되므로 디폴트 값은 타입으로 가지고 호출하는 것은 메모리 즉, 포인터로 정해진 함수를 읽어온다.!!!