가상함수는 C++ 에서 매우 중요한 위치를 차지하는 문법이다.

기초 클래스의 포인터로 객체를 참조하면,

int main(void)
{
	Base *bptr = new Derived(); // 컴파일 OK
	bptr->DerivedFunc(); //컴파일 error!
}

“DerivedFunc는 Base클래스의 멤버가 아닙니다.”라는 메시지를 전달하면서 컴파일 에러를 발생시킨다.

C++ 컴파일러는 포인터 연산의 가능성 여부를 판단 할 때, 포인터의 자료형을 기준으로 판단하지, 실제 가리키는 객체의 자료형을 기준으로 판단하지는 않는다.

int main(void)
{
	Base *bptr = new Derived(); //컴파일 OK!
	Derived *dptr = bptr; // 컴파일 Error!
}

컴파일러는 앞서 bptr이 실제로 갈리키는 객체가 Derived 객체라는 사실을 기억하지 않는다. 그리고는 다음과 같이 판단을 하고 컴파일 에러를 발생시킨다.

“bptr은 base형 포인터이니까, bptr이 가리키는 대상은 Base 객체일 수도 있는 거잖아! 그럴 경우에는 이 문장이 성립하지 않으니까, 컴파일 에러를 발생시켜야겠다.”

반면 다음의 코드는 문제없이 컴파일이 된다.

int main(void)
{
	Derived *dptr = new Derived();
	Base *bptr = dptr;
}

함수의 오버라이딩과 포인터 형

int main(void)
{
	Third *tptr = new Third();
	Second *sptr = tptr;
	First *fptr = sptr;
	
	fptr->MyFunc(); //calls first func
	sptr->MyFunc(); //calls second func
	tptr->MyFunc(); //calls third func
}

가상함수 (Virtual Function)

“함수를 오버라이딩을 했다는 것은, 해당 객체에서 호출되어야 하는 함수를 바꾼다는 의미인데, 포인터 변수의 자료형에 따라서 호출되는 함수의 종류가 달라지는 것은 문제가 있어 보입니다.”

그래서 C++ 은 이러한 상황이 발생하지 않도록 ‘가상함수’라는 것을 제공하고 있다. 그런데 이 가상함수라는 것은 C++ 의 개념이 아닌 객체지향의 개념이다. 따라서 C++ 뿐만이 아니라 JAVA, C# 과 같은 객체지향 언어에서도 이와 동일한 개념의 문법이 제공되고 있다.

class First
{
	public:
		virtual void MyFunc() {cout << "FirstFunc"<<endl;}
}

이렇게 가상함수가 선언되고 나면, 이함수를 오버라이딩 하는 함수도 가상함수가 된다. 따라서 위와 같이 First 클래스의 MyFunc 함수가 virtual로 선언되면, 이를 오버라이딩 하는 Second 클래스의 MyFunc 함수도, 그리고 이를 오버라이딩 하는 Third 클래스의 함수도 가상함수가 된다.


함수가 가상함수로 선언되면, 해당 함수호출 시, 포인터의 자료형을 기반으로 호출대상을 결정하지 않고, 포인터 변수가 실제로 가리키는 객체를 참조하여 호출의 대상을 결정한다.


순수 가상함수 (Pure Virtual Function)와 추상 클래스(Abstract Class)