C++에서 가상 함수(virtual function)는 기본 클래스 포인터 목록을 만들어, 실제 파생 클래스 객체의 타입을 몰라도 해당 객체의 메서드를 호출할 수 있도록 해주는 강력한 기능입니다. 가상 함수는 컴파일 시점이 아닌 런타임에 늦은 바인딩(late binding) 방식으로 결정됩니다.
가상 함수의 핵심 역할: 런타임 다형성
가상 함수의 가장 중요한 용도는 런타임 다형성(Runtime Polymorphism)을 구현하는 것입니다. 런타임 다형성은 오직 기본 클래스 타입의 포인터(또는 참조)를 통해서만 구현할 수 있습니다.
기본 클래스 포인터는 기본 클래스 자신의 객체뿐만 아니라, 그로부터 파생된 클래스의 객체도 가리킬 수 있습니다. 따라서 하나의 포인터 변수로 서로 다른 파생 클래스들의 동작을 유연하게 처리할 수 있으며, 이것이 바로 다형성의 본질입니다.
예제 코드
아래 예제에서는 기본 클래스 포인터 b가 파생 클래스 객체 d의 주소를 저장하고, 가상 함수를 통해 파생 클래스의 메서드가 호출되는 과정을 보여줍니다.
#include<iostream>
using namespace std;
class B {
public:
virtual void s() {
cout << " In Base \n";
}
};
class D : public B {
public:
void s() {
cout << "In Derived \n";
}
};
int main(void) {
D d; // 클래스 D의 객체 생성
B *b = &d; // B* 타입 포인터가 d를 가리킴
b->s(); // "In Derived" 출력
return 0;
}실행 결과
In Derived
동작 원리 살펴보기
만약 기본 클래스의 s() 함수에 virtual 키워드가 없었다면, 포인터 b의 정적 타입(static type)을 기준으로 컴파일 시점에 B::s()가 호출되어 " In Base "가 출력되었을 것입니다.
하지만 virtual 키워드가 붙으면 컴파일러는 가상 함수 테이블(vtable)이라는 메커니즘을 통해 객체의 실제 타입을 확인하고, 런타임에 D::s()를 호출합니다. 이처럼 어떤 함수가 실행될지 실행 도중에 결정되기 때문에 이를 동적 바인딩(dynamic binding) 또는 늦은 바인딩(late binding)이라고 부릅니다.
정리
- 가상 함수는
virtual키워드로 선언하며, 파생 클래스에서 재정의(오버라이드)할 수 있습니다. - 기본 클래스 포인터나 참조를 통해 호출해야 런타임 다형성이 발휘됩니다.
- 함수 호출 결정이 런타임으로 미뤄지므로 유연하고 확장 가능한 코드 작성이 가능합니다.
- 객체 지향 설계에서 인터페이스 추상화와 프레임워크 개발의 핵심 기반 기술입니다.