정의

부모 클래스의 포인터나 레퍼런스가 자식 클래스 객체를 가리킬 때
자식 클래스의 가상 함수 재정의(Override)에 의해 호출되는 함수가 달라지는 것

다형성의 좋은 예이다.

A클래스에 virtual Func()가 있고 B클래스가 A클래스를 상속 받고 Func()함수를 재정의(Override)했을때
A* a = new B();
a->Func(); 를 하면
컴파일러는 Func()가 A의 Func()를 실행해야하는지 B의 Func()를 실행해야하는 알지를 못합니다.
그럼 어떻게 알아내야 할까??
객체 타입을 런타임중에 알아내는 매커니즘을 RTTI(Run Time Type Information)이라고 하는데
RTTI는 다음 글에 정리하겠습니다.

C++은 가상 함수를 처리하기 위해서 가상 함수를 갖는 클래스 마다 '가상 함수 테이블(Virtual function table)을 생성하는데 가상 함수 테이블은 가상 함수 포인터를 모아둔 배열입니다.

가상함수 포인터(vfptr)는 가상함수를 추가할 경우 객체의 멤버 변수로 가상 함수 테이블을 가리키는 포인터를 멤버 변수로 추가를 시킵니다.

일단 가상 함수 포인터가 실제로 추가되는지 부터 확인하겠습니다.

코드

#include <iostream>

class A
{
private:
	int m_i = 0;
public: 
	void Func()
	{

	}
};

class B : public A
{

};

int main()
{
	A a;
	B b;
	return 0;
}

다음과 같은 코드가 있을때 a와b를 살펴보자.
멤버 변수에 int m_i만 존재한다.

virtual 함수를 추가하면 어떻게 변할까??

#include <iostream>

class A
{
private:
	int m_i = 0;
public: 
	virtual void Func()
	{

	}
};

class B : public A
{

};

int main()
{
	A a;
	B b;
	return 0;
}

가상 함수를 만들자 __vfptr이 추가되었다. 
B클래스에서 Func()함수를 재정의 하지 않았기 때문에 
a와 b의 __vfptr를 보면 Func()함수 포인터 주소를 0x00ed13a7로 같은 곳을 가리키고있다. 

일단 B클래스에서 Func()함수를 재정의 해보겠다.

#include <iostream>

class A
{
private:
	int m_i = 0;
public: 
	virtual void Func()
	{

	}
};

class B : public A
{
public:
	void Func() override
	{

	}
};

int main()
{
	A a;
	B b;
	return 0;
}

B클래스에서 void Func() override로 재정의를 하였다.
override는 암시적으로도 붙기때문에 명시적으로 작성할 필요는 없지만 저는 재정의 했다는걸 한눈에 알 수 있게
작성하는걸 선호하는 편입니다.

자이제 보면 a는 A::Func() 0x009a12ee를 가리키고있고 b는 B::Func() 0x009a1366을 가리키고 있습니다.
재정의 하지 않았을때 B의 Func()는 A의 Func() 포인터를 가리키고 있었지만 재정의하면서 B의 Func()를 가리킵니다.

이렇게 가상 함수테이블에 가상 함수 포인터가 추가되기 때문에 자기의 클래스에 맞는 함수를 찾아갑니다.

그럼 A의 클래스를 100개 만들면 가상함수테이블도 100개 만들어질까???
답은 X입니다.
가상 함수 테이블은 가상 함수가 있는 클래스마다 1개씩만 생성이됩니다.

int main()
{
	A a1;
	A a2;
	A a3;
	A a4;
	B b;
	return 0;
}

메인함수를 위와 같이 수정후 실행하면

가상함수테이블의 주소가 0x00987b34로 다 같은걸 확인할 수 있습니다.

 

))여담

전 포스트에서 바이트 패딩에 대해 정리했었는데
가장큰 자료형의 크기로 데이터가 정렬이 됬었는데
가상 함수를 사용하면 멤버 변수로 가상함수포인터 __vfptr이 생겼다.
그럼 64비트 환경에서 
멤버변수로 
int (4byte)
short (2byte)
__vfptr (8byte) 이 있으면 바이트패딩이 이루어질까?
64비트 환경이기에 포인터의 크기는 8바이트
그럼 8(4,2) + 8 = 16이 나와야한다. 

바이트 패딩이 가장 큰 크기인 __vfptr의 크기로 된다.

'게임 프로그래밍 > C++ 기초' 카테고리의 다른 글

C++ 가변 인자 템플릿  (0) 2020.05.05
C++ 가변인자  (1) 2020.05.03
C++ 자료형의 type을 알아내는 방법 typeid()  (0) 2020.05.02
C++ 다형성(Polymorphism)  (0) 2020.05.02
C++ Lvalue , Rvalue  (3) 2020.04.04

+ Recent posts