C++에서 가상 함수(virtual function)는 기반 클래스(base class) 포인터 목록을 만들어, 실제 객체가 어떤 파생 클래스(derived class)에 속하는지 알지 못한 채로도 해당 파생 클래스의 메서드를 호출할 수 있게 해주는 강력한 기능입니다. 이러한 가상 함수는 컴파일 시점이 아닌 런타임(runtime)에 늦은 바인딩(late binding) 방식으로 해석됩니다.
가상 함수의 상속 규칙
기반 클래스에서 한 번 가상 함수로 선언된 멤버 함수는, 그 기반 클래스로부터 파생되는 모든 클래스에서 자동으로 가상 함수가 됩니다. 따라서 파생 클래스에서 가상 함수를 재정의(redefine)할 때는 virtual 키워드를 다시 붙일 필요가 없습니다.
이 규칙 덕분에 코드를 더 간결하게 유지할 수 있으며, 다형성(polymorphism)을 활용한 설계가 한층 수월해집니다.
예제 코드
#include<iostream>
using namespace std;
class B {
public:
virtual void s() {
cout << " In Base \n";
}
};
class D : public B {
public:
void s() { // virtual 키워드 없이 재정의 가능
cout << "In Derived \n";
}
};
int main(void) {
D d; // D 클래스의 객체 생성
B *b = &d; // B* 타입 포인터가 d를 가리킴
b->s(); // 런타임에 D::s() 호출 결정
return 0;
}실행 결과
In Derived
코드 설명
위 예제에서 포인터 b는 타입이 B*임에도 불구하고, 실제로 가리키는 객체는 D> 클래스의 인스턴스입니다. 함수 s()가 기반 클래스에서 virtual로 선언되었기 때문에, 호출 시점에 객체의 실제 타입을 기준으로 D::s()가 실행됩니다.
만약 virtual 키워드가 없었다면 포인터의 정적 타입을 따라 B::s()가 호출되었을 것입니다. 이것이 바로 가상 함수가 제공하는 동적 디스패치(dynamic dispatch)의 핵심입니다.
정리
- 기반 클래스에서
virtual로 선언된 함수는 모든 파생 클래스에서 자동으로 가상 함수가 됩니다. - 파생 클래스에서 재정의 시
virtual키워드는 생략할 수 있습니다(붙여도 무방하며, 가독성을 위해 명시하기도 합니다). - 기반 클래스 포인터나 참조를 통해 호출하면 런타임에 실제 객체 타입에 맞는 함수가 실행됩니다.