가상 함수( virtual function )
가상 함수( virtual function )는 기본( base ) 클래스에서 virtual 키워드를 사용하여 선언되고, 파생 클래스에서 재정의( overriding )되는 멤버 함수를 말합니다.
이러한 가상 함수가, 파생 클래스에서 재정의 되는, 일반 멤버 함수와 다른 점은, 파생 클래스가 기본( base ) 클래스 타입의 포인터나 참조를 통해 접근될 때, 가상 함수를 호출함으로써, 기본 클래스의 함수가 아닌 파생 클래스 버전의 함수를 사용할 수 있다는 점에 있습니다.
코드를 보는 것이 이해하는데 더 도움이 될 것입니다.
#include <iostream>
using namespace std;
class CBase{ // 기본 클래스
public:
virtual void VFunc(){ // 가상 함수
cout << "Base Virtual Func called\n";
}
void Func(){ // 일반 멤버 함수
cout << "Base Func called\n";
}
};
class CDerived : public CBase { // 파생 클래스
public:
virtual void VFunc(){ // 가상 함수 재정의
cout << "CDerived Virtual Func called\n";
}
void Func(){ // 일반 멤버 함수 재정의
cout << "CDerived Func called\n";
}
};
int main(){
CDerived cObj;
CBase& rObj = cObj; // Base 클래스로 참조
rObj.Func(); // Base 클래스의 함수 호출
rObj.VFunc(); // CDerived 클래스의 함수 호출
}
▼출력
Base Func called
CDerived Virtual Func called
위 예제를 보면, 기본 클래스 CBase
의 가상함수 VFunc와 일반 함수 Func를 파생 클래스 CDerived
에서 재정의( overriding ) 하고 있습니다.
그리고 main 함수에서 파생 클래스 타입의 객체를 기본(base) 클래스 타입의 참조( reference ) 변수 rObj
로 참조하고 있습니다.
이때, rObj.Func()
코드는 CBase
클래스의 Func 함수를 호출하지만, rObj.VFunc()
코드는 CDerived
클래스의 VFunc 함수를 호출하게 됩니다.
이것이 가상 함수의 기능이며, 이렇게 기본 클래스( CBase
) 타입의 참조를 통해 파생 클래스( CDerived
)의 기능을 사용하는 것을 다형성(polymorphism)이라고 합니다.
이러한 가상 함수는 클래스 형식의 개체에 대해서만 호출되므로, 전역( global ) 또는 정적( static ) 함수를 가상 함수로 선언할 수 없습니다.
그리고, 기본 클래스에서 가상 함수로 선언한 함수는, 재정의( overriding ) 시에도 항상 가상 함수입니다.
따라서, 파생 클래스에서 반드시 virtual 키워드를 사용해야 하는 것은 아닙니다.
class CDerived : public CBase {
public:
// 가상 함수 재정의, virtual 키워드를 사용하지 않아도 됩니다.
void VFunc(){
cout << "CDerived Virtual Func called\n";
}
};
가상 함수( virtual function )를 사용하는 이유
가상 함수를 사용하는 이유는, 위에서 말한, 다형성( polymorphism )을 실현하고자 하기 때문입니다.
이 다형성( polymorphism )은 객체 지향 프로그래밍의 특징 중 하나이고, "여러 형태"를 의미하는 그리스어 단어로, 가상 함수를 사용하여, 어떤 식으로 객체가 다양한 형태를 띨 수 있는지를 예문을 통해 알아보겠습니다.
아래의 CShape
클래스는 CRectangle
, CCircle
, CTriangle
클래스들의 기본 클래스입니다.
#include <iostream>
using namespace std;
class CShape{ // base 클래스
public:
virtual float CalcArea(){ // 가상 함수
cout << "Shape Area: " << 0 << endl;
return 0;
}
};
class CRectangle : public CShape {
float width, height;
public:
CRectangle(float w, float h) : width(w), height(h) {}
virtual float CalcArea(){ // 가상 함수
float fArea = width * height;
cout << "Rectangle Area: " << fArea << endl;
return fArea;
}
};
class CCircle : public CShape {
float radius;
public:
CCircle(float r) : radius(r){}
virtual float CalcArea(){ // 가상 함수
float fArea = radius*radius*3.14;
cout << "Circle Area: " << fArea << endl;
return fArea;
}
};
class CTriangle : public CShape {
float width, height;
public:
CTriangle(float w, float h) : width(w), height(h) {}
virtual float CalcArea(){ // 가상 함수
float fArea = width * height * 1/2;
cout << "Triangle Area: " << fArea << endl;
return fArea;
}
};
int main(){
CShape* pObj;
CRectangle cRect(20, 20); // 다양한 도형 객체
CCircle cCircle(10);
CTriangle cTri(20, 20);
pObj = &cRect;
float fArea = pObj->CalcArea(); // 사각형 역할
pObj = &cCircle;
fArea = pObj->CalcArea(); // 원의 역할
pObj = &cTri;
fArea = pObj->CalcArea(); // 삼각형 역할
}
▼출력
Rectangle Area: 400
Circle Area: 314
Triangle Area: 200
위에 예제에서 CShape
타입의 포인터 pObj
가 어떤 객체를 가리키는 가에 따라, 가상 함수를 통해 그 객체의 기능을 사용할 수 있음을 알 수 있습니다.
예를 들어, pObj
가 CRectangle
타입의 객체를 가리키면, pObj->CalcArea
는 CRectangle
클래스의 CalcArea 가상 함수를 호출할 것입니다.
이것이 바로 다형성입니다.
만약, 가상 함수가 없다면 도형들의 면적을 구하는 함수 구현은 다음처럼 될 것입니다.
float CalcArea( CRectangle& cObj){
return cObj.CalcArea();
}
float CalcArea( CCircle& cObj){
return cObj.CalcArea();
}
float CalcArea( CTriangle& cObj){
return cObj.CalcArea();
}
그리고, 도형이 늘어날수록, 이 함수의 리스트도 같이 늘어나게 될 것입니다.
그러나, 가상 함수를 사용하면 다음과 같이 구현할 수 있습니다.
float CalcArea( CShape& cObj){
return cObj.CalcArea();
}
cObj
에 CShape
파생 타입의 어떤 객체를 대입하더라도, 파생 클래스에서 CalcArea 가상 함수를 재정의 했다면, 그 객체의 형태로 기능을 수행해 원하는 목표를 달성하게 될 것입니다.
● 참고로, 위의 CRectangle
클래스에서 기본 클래스 CShape
의 가상 함수를 호출하고 싶다면 어떻게 해야 할까요?
이 때는 범위 지정 연산자( scope resolution operator ) ::
을 사용해 기본 클래스의 함수 호출을 명시함으로써, 가상 함수 호출 메커니즘을 억제할 수 있습니다.
int main(){
CRectangle cRect(20, 20);
cRect.CalcArea(); // CRectangle 함수 호출
cRect.CShape::CalcArea(); // 명시적 CShape 함수 호출
CShape* pObj = &cRect;
pObj->CalcArea(); // CRectangle의 가상 함수 호출
pObj->CShape::CalcArea(); // 명시적 CShape 함수 호출
}
▼출력
Rectangle Area: 400
Shape Area: 0
Rectangle Area: 400
Shape Area: 0
정리
- 가상 함수를 통해서 기본(base) 클래스 타입의 포인터나 참조( reference )로 파생 클래스의 함수를 호출할 수 있습니다.
- 가상 함수를 쓰는 이유는 다형성( polymorphism )을 구현하기 위해서입니다.
이 글과 관련 있는 글들
가상 함수를 호출하기 위한 동적 바인딩( dynamic binding )
추상 클래스( abstract class )와 순수 가상 소멸자