Inheritance의 이해 - ② 가상 함수 테이블(Virtual table)
virtual 키워드를 사용해서 멤버함수를 재정의할 경우 메모리에서 어떤 일이 일어나는지 가상 함수 테이블을 중심으로 살펴본다. 우선 아래와 같이 virtual을 사용하지 않은 경우를 살펴보자.
class Animal {
public:
//virtual ~Animal() = default;
void Speak()
{
std::cout << "Animal" << std::endl;
}
private:
double weight;
};
class Cat : public Animal {
public:
void Speak()
{
std::cout << "mew~" << std::endl;
}
private:
double height;
};
int main()
{
Animal *animal = new Animal();
Animal *cat = new Cat();
animal->Speak();
cat->Speak();
std::cout << "Animal:" << sizeof(Animal) <<" Cat: " << sizeof(Cat) << std::endl;
return 0;
}
Animal
Animal
Animal:8 Cat: 16
출력 결과에서 두 가지 특징을 살펴볼 수 있다.
- Animal* 타입의
cat
은 실제 Cat 클래스의 인스턴스지만 Animal 클래스의 Speak() 함수를 사용한다. - Animal의 인스턴스의 크기는 8byte(weight) 이고, Cat 인스턴스 크기는 16byte(weight, height)이다.
animal
과 cat
인스턴트의 구조를 살펴보면 다음과 같을 것이다.
가상함수 테이블 생성
이번에는 Animal의 Speak 함수와 소멸자를 다음과 같이 가상 함수(virtual)로 선언해보자.
class Animal {
public:
virtual ~Animal() = default;
virtual void Speak()
{
std::cout << "Animal" << std::endl;
}
private:
double weight;
};
class Cat : public Animal {
public:
void Speak() override
{
std::cout << "mew~" << std::endl;
}
private:
double height;
};
int main()
{
Animal *animal = new Animal();
Animal *cat = new Cat();
animal->Speak();
cat->Speak();
std::cout << "Animal:" << sizeof(Animal) <<" Cat: " << sizeof(Cat) << std::endl;
return 0;
}
Animal
mew~
Animal:16 Cat: 24
여기서 두가지 특징을 살펴보자.
- Animal* 타입의
cat
이 실형식 Cat의 Speak() 함수를 호출했다. - Animal과 Cat의 객체크기가 8byte씩 증가하였다.
첫 번째 특징에서 우리는 다형성(Polymorphism)을 확인할 수 있다. 그리고 우리가 별다른 멤버 변수를 선언하지도 않았는데 객체의 크기가 8byte만큼 증가한 것을 확인할 수 있다. 이 8byte는 가상 함수 테이블의 주소 값이다.
virtual 키워드를 사용하여서 가상 함수를 만들게 되면 컴파일러는 가상함수 테이블(Virtual Function Table / vftable)을 만들고 객체에는 어느 가상함수 테이블을 참조할지 정하는 가상함수 포인터(Virtual Function Pointer / vfptr)를 만든다. 그리고 생성되는 인스턴스의 실형식에 따라서 어느 가상 함수 테이블을 참조할지를 결정하여 Dynamic Polymorphism을 구현할 수 있게 된다.
출처: https://www.youtube.com/channel/UCHcG02L6TSS-StkSbqVy6Fg