본문 바로가기

[C++] 가상 함수 (Virtual Function)

Kwonriver 2022. 1. 8.
728x90

 

상속간에 있는 클래스의 자식클래스를 부모클래스에 저장했다고 가정한다.

Parent* p = new Child();

그리고 이 때 Child에만 있는 함수를 실행하면 실행에 실패한다.

p는 자료형이 Parent이고 Parent에 없는 멤버 함수는 실행할 수 없기 때문이다.

따라서 Child에만 있는 함수를 사용하기 위해서는 강제형변환을 해줘야한다.

 

그렇다면 재정의한 함수는 어떻게 될까?

p는 자료형이 Parent이기 때문에 Parent에 있는 함수가 실행된다

즉, Child에서 재정의한 함수를 사용하기 위해서는 강제형변환을 통해 변환 후 사용이 가능하다

(static_cast<Child>(p))->overridingFunc()

참으로 거추장스럽고 지저분해보인다

이를 간편하게 강제형변환 하지 않고 사용하기 위해 가상함수를 사용한다.

 

가상함수를 만들기 위해서는 virtual 키워드를 사용하여 선언한다

Parent 클래스 내부에 virtual void overridingFunc()라는 함수가 있다고 가정하고

Child 클래스 내부에서 void overridingFunc()로 재정의 하였다면 Child의 함수도 가상함수가 된다

즉, 가상함수를 재정의한 함수도 자동으로 가상함수가 된다

그렇다면 가상함수를 사용하면 무엇이 달라질까?

 

앞서 이야기했던 재정의한 함수의 문제를 해결할 수 있게 된다

Parent*에 저장되어 있던 메모리의 원래 자료형은 Child였다 ( 동적할당할 때 그렇게 했으니까 )

따라서 p->overridingFunc() 를 사용하면 원래의 자료형인 Child의 overridingFunc()가 호출된다.

 

내부적으로 클래스에 virtual 즉, 가상함수가 존재하면 vptr이 생성되고 vptr vtable을 가리키게 된다.

vtable은 각 클래스 하나씩 존재하게 되며 가상함수의 주소를 가지고 있다.

가상함수를 호출하게 되면 vtpr-> vtable의 함수를 호출하게 된다.

 

사용 예 

class Car
{
private:
	int lotNumber;
public:
	Car() : lotNumber(0) { }
	Car(int Number) : lotNumber(0)
	{
	}

	void Brake(float fPower);
};

void Car::Brake(float fPower)
{
	std::cout << "Car클래스 브레이크 동작 힘 : " << fPower << std::endl;
}


class SportCar : public Car
{
public:
	SportCar(int number);
	
	void Brake(float fPower);
};

class SUV : public Car
{
public:
	SUV(int number);

	void Brake(float fPower);
};

class Bus : public Car
{
public:
	Bus(int number);

	void Brake(float fPower);
};

SportCar::SportCar(int number)
{
}

void SportCar::Brake(float fPower)
{
	std::cout << "Sport Car 클래스 브레이크 동작 힘 : " << fPower << std::endl;
}

SUV::SUV(int number)
{
}

void SUV::Brake(float fPower)
{
	std::cout << "SUV 클래스 브레이크 동작 힘 : " << fPower << std::endl;
}

Bus::Bus(int number)
{
}

void Bus::Brake(float fPower)
{
	std::cout << "Bus 클래스 브레이크 동작 힘 : " << fPower << std::endl;
}

int main()
{
	// 자식클래스의 생성자 호출
	Car* pArrMyCars[10] = { 0, };
	pArrMyCars[0] = new SUV(0);
	pArrMyCars[1] = new Bus(1);
	pArrMyCars[2] = new SportCar(2);
	pArrMyCars[3] = new Car(3);

	for (int i = 0; i < 10; i++)
	{
		if (pArrMyCars[i] == NULL)
			continue;   

		pArrMyCars[i]->Brake(0.5f);
		delete pArrMyCars[i];
	}
}

위 코드는 가상함수를 사용하지 않은 채 상속받아 함수 재정의를 하였다.

결과를 보면 pArrMyCars의 원래 자료형인 Car*의 Brake만 호출된다.

Car의 Brake만 호출

가상함수를 사용한 아래 코드를 보자

class Car
{
private:
	int lotNumber;
public:
	Car() : lotNumber(0) { }
	Car(int Number) : lotNumber(0)
	{
	}

	virtual void Brake(float fPower);
};

void Car::Brake(float fPower)
{
	std::cout << "Car클래스 브레이크 동작 힘 : " << fPower << std::endl;
}


class SportCar : public Car
{
public:
	SportCar(int number);
	
	void Brake(float fPower);
};

class SUV : public Car
{
public:
	SUV(int number);

	void Brake(float fPower);
};

class Bus : public Car
{
public:
	Bus(int number);

	void Brake(float fPower);
};

SportCar::SportCar(int number)
{
}

void SportCar::Brake(float fPower)
{
	std::cout << "Sport Car 클래스 브레이크 동작 힘 : " << fPower << std::endl;
}

SUV::SUV(int number)
{
}

void SUV::Brake(float fPower)
{
	std::cout << "SUV 클래스 브레이크 동작 힘 : " << fPower << std::endl;
}

Bus::Bus(int number)
{
}

void Bus::Brake(float fPower)
{
	std::cout << "Bus 클래스 브레이크 동작 힘 : " << fPower << std::endl;
}

int main()
{
	// 자식클래스의 생성자 호출
	Car* pArrMyCars[10] = { 0, };
	pArrMyCars[0] = new SUV(0);
	pArrMyCars[1] = new Bus(1);
	pArrMyCars[2] = new SportCar(2);
	pArrMyCars[3] = new Car(3);

	for (int i = 0; i < 10; i++)
	{
		if (pArrMyCars[i] == NULL)
			continue;   

		pArrMyCars[i]->Brake(0.5f);
		delete pArrMyCars[i];
	}
}

Brake라는 함수를 가상함수로 만들었다.

이렇게 되면 가장 아래 for문에서 Brake 가 호출 될 때 재정의된 함수가 동작한다.

재정의 한 함수가 없다면 부모의 함수가 호출된다.

형변환 없이 각 함수가 호출된 모습

 

728x90

'[프로그래밍 공부] > C&C++' 카테고리의 다른 글

[C++] 범위 기반 for 문  (0) 2022.01.22
[C] while문 안에서 scanf 받기  (0) 2022.01.13
[C++] 스마트 포인터  (0) 2022.01.08
[C++] 클래스 상속(inheritance)  (0) 2022.01.01
[C++] 클래스 생성자와 소멸자  (0) 2022.01.01