C++에서 가상 함수(virtual function)는 기본 클래스 포인터 목록을 활용하여, 파생 클래스 객체의 실제 타입을 알지 못해도 해당 클래스의 메서드를 호출할 수 있도록 해주는 기능입니다. 가상 함수는 컴파일 시점이 아니라 런타임에 늦게 바인딩(late binding)된다는 특징이 있습니다.
가상 함수와 인라인 함수의 역할
가상 함수의 핵심 목적은 런타임 다형성(Runtime Polymorphism)을 구현하는 것입니다. 반면 인라인 함수(inline function)는 코드 실행 효율을 높이기 위해 사용됩니다. 인라인 함수가 호출되면 컴파일러는 함수 호출 오버헤드를 줄이기 위해 호출 지점에 함수 본문 코드를 직접 대체(substitution)합니다.
언제 인라인화가 가능할까?
여기서 중요한 규칙이 등장합니다.
- 기본 클래스의 참조(reference)나 포인터(pointer)를 통해 가상 함수를 호출하는 경우: 인라인화될 수 없습니다. 어떤 파생 클래스의 함수가 호출될지 런타임에 결정되기 때문입니다.
- 참조나 포인터 없이 객체 자체를 통해 호출하는 경우: 인라인화가 가능합니다. 컴파일러가 컴파일 시점에 객체의 정확한 클래스를 알 수 있기 때문입니다.
예제 코드
#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) {
B b;
D d; // D 클래스의 객체
B *bptr = &d; // d를 가리키는 B* 타입의 포인터
b.s(); // 객체를 통해 호출했으므로 인라인화 가능
bptr->s(); // "In Derived" 출력
// 포인터를 통해 가상 함수가 호출되었으므로 인라인화 불가능
return 0;
}실행 결과
In Base In Derived
정리
b.s()처럼 객체를 직접 사용해 호출하면 컴파일러가 호출 대상을 정확히 알기 때문에 인라인 최적화가 적용됩니다. 반면 bptr->s()처럼 기본 클래스 포인터를 통해 호출하면 실제 호출될 함수가 런타임에 결정되므로, 가상 함수라 할지라도 인라인화될 수 없습니다. 따라서 성능이 중요한 코드에서는 가상 함수 호출 방식을 신중하게 선택하는 것이 좋습니다.