virtual이 선언된 클래스가 있는 경우, 가상 함수 테이블은 rdata 섹션에 생성된다.
가상 함수 테이블(vtable)은 함수 포인터 배열이며,
이 포인터를 따라가면 가상 함수로 선언된 멤버 함수들의 주소에 배열 형태로 접근할 수 있다.
즉, 가상 함수 테이블이 실제 호출되어야 할 함수의 위치를 저장하고 있다.
가상 함수 테이블을 확인하기 위해 아래의 코드를 실행해보자.
#include <stdio.h>
#include <iostream>
using namespace std;
class FOOD
{
private:
int price = 0;
public:
FOOD() { cout << "FOOD Constructor" << endl; }
virtual ~FOOD() { cout << "FOOD deleted " << endl; }
virtual void printName() { cout << "FOOD Class" << endl; }
virtual void printPrice() { cout << this->price << endl; }
void printLine() { cout << "=================" << endl; }
};
class SNACK : public FOOD
{
private:
int price = 1000;
public:
SNACK() { cout << "SNACK Constructor" << endl; }
virtual ~SNACK() { cout << "SNACK deleted " << endl; }
void printName() { cout << "SNACK Class" << endl; }
void printPrice() { cout << "Snack : " << this->price << endl; }
};
int main(void)
{
FOOD* fd = new SNACK();
fd->printName();
delete fd;
return 0;
}
FOOD와 SNACK의 생성자에 F9로 break point를 잡는다.
F5를 이용해 debug 모드를 실행한다.
최초로 쓰레기 값이 할당되어 있는 것을 알 수 있다. F5로 다음으로 넘어간다.
FOOD는 price = 0이다. 그리고 FOOD의 __vfptr을 볼 수 있다.
SNACK의 생성자에서 break 되었다.
price = 1000으로 할당 되었고 __vfptr의 함수 포인터들이 변경된 것을 알 수 있다.
변경된 함수 포인터는 destructor, printName, printPrice다.
내부적으로 각 생성자에서 __vfptr에 각 클래스의 가상 함수 테이블의 주소를 할당하고 있다.
따라서 SNACK의 printName을 호출하는 것은 실제로 fd->__vfptr->printName(); 이다.
여기서 __vfptr은 SNACK 생성자에서 할당된 vtable이 된다.
이제 실행한 코드에서 virtual을 지우고 위와 같이 테스트해보자.
#include <stdio.h>
#include <iostream>
using namespace std;
class FOOD
{
private:
int price = 0;
public:
FOOD() { cout << "FOOD Constructor" << endl; }
~FOOD() { cout << "FOOD deleted " << endl; }
void printName() { cout << "FOOD Class" << endl; }
void printPrice() { cout << this->price << endl; }
void printLine() { cout << "=================" << endl; }
};
class SNACK : public FOOD
{
private:
int price = 1000;
public:
SNACK() { cout << "SNACK Constructor" << endl; }
~SNACK() { cout << "SNACK deleted " << endl; }
void printName() { cout << "SNACK Class" << endl; }
void printPrice() { cout << "Snack : " << this->price << endl; }
};
int main(void)
{
FOOD* fd = new SNACK();
fd->printName();
delete fd;
return 0;
}
최초의 쓰레기 값은 아래와 같이 할당되었다.
FOOD class의 price = 0이 할당 되었다.
virtual 함수를 가지지 않기 때문에 __vfptr이 없다.
SNACK의 생성자가 호출되었어도 역시 __vfptr이 없다.
따라서 가상 함수 테이블은 가상 함수를 가지고 있는 클래스에서만 생성된다.
Late binding
바인딩 - 함수나 변수의 주소가 결정되는 것.
컴파일 타임에 함수나 변수의 주소가 결정되는 것은 Early binding이고,
런타임에 결정되는 경우는 Late binding이다. 또는 동적 바인딩 Dynamic binding이라고도 한다.
가상 함수는 Late binding의 한 예시가 된다.
'개발 > C, C++' 카테고리의 다른 글
인터페이스 vs 추상 클래스 (Java, C++ 비교) (0) | 2022.02.05 |
---|---|
C++ - 순수 가상 클래스 (Pure Virtual Class) (0) | 2021.11.10 |
C++ - 이동 생성자 (move constructor) (0) | 2021.10.29 |
C++ - 임시 객체 (temporary object) (0) | 2021.10.27 |
C++ - 우측값 참조 r-value reference (기본 자료형) (0) | 2021.10.25 |
댓글