아래는 **가상 함수(virtual function)**에 대해 초보자와 상급자를 위한 두 가지 설명입니다.
초보자용 설명: 가상 함수란?
가상 함수는 객체 지향 프로그래밍에서 사용되는 기능으로, 부모 클래스(기본 클래스)에 정의된 함수를 자식 클래스(파생 클래스)에서 **재정의(오버라이딩)**할 수 있게 해줍니다.
- 목적:
- 다형성(Polymorphism)을 구현하기 위해 사용됩니다.
- 부모 클래스의 포인터나 참조를 사용해 자식 클래스의 객체를 다룰 때, 올바른(자식 클래스에서 재정의된) 함수를 호출하도록 보장합니다.
→ 그림 작업 중 **“스케치”**를 미리 해두어, 이후 작업에 있어 기초 “구조”를 엇나가지 않게 잡아주는 역할이라고 생각해도 좋습니다.
- 예시:
-
상황: 여러분이 동물(Animal)이라는 클래스를 만들고, 그 안에 makeSound()
라는 함수를 정의했다고 합시다.
-
부모 클래스:
class Animal {
public:
virtual void makeSound() {
std::cout << "Some generic animal sound" << std::endl;
}
};
-
자식 클래스:
class Dog : public Animal {
public:
void makeSound() override { // Dog 클래스에서는 재정의
std::cout << "Bark!" << std::endl;
}
};
-
결과:
Animal* animal = new Dog();
animal->makeSound(); // "Bark!"가 출력됩니다.
- 여기서
makeSound()
가 가상 함수이기 때문에, Animal
포인터를 통해 Dog
객체의 재정의된 makeSound()
가 호출됩니다.
즉, 가상 함수는 같은 이름의 함수라도 객체의 실제 타입에 따라 다른 동작을 수행하도록 만드는 중요한 도구입니다.
상급자용 설명: 가상 함수의 원리와 응용
1. 동적 바인딩(Dynamic Binding)과 vtable
- 동적 바인딩:
- 가상 함수 호출은 런타임에(실행 도중) 호출할 함수를 결정합니다.
- 이는 정적 바인딩(컴파일 타임에 호출할 함수가 결정되는 방식)과 대조되며, 동적 바인딩은 다형성을 가능하게 합니다.
- vtable(가상 함수 테이블):
- 컴파일러는 각 클래스에 대해 vtable이라는 테이블을 생성합니다.
- vtable은 해당 클래스의 가상 함수들에 대한 포인터 배열입니다.
- 각 객체는 자신의 클래스에 해당하는 vtable을 가리키는 숨겨진 포인터(예: vptr)를 포함합니다.
- 가상 함수 호출 시, 객체의 vptr을 통해 vtable에 접근하여 올바른 함수 포인터를 찾아 호출하게 됩니다.
2. 가상 함수 호출 최적화: Devirtualization
- 문제점:
- 가상 함수 호출은 vtable 조회라는 간접 호출(indirect call) 과정을 포함하므로, 일반적인 직접 호출에 비해 약간의 오버헤드가 발생합니다.
- 최적화 기법 (Devirtualization):
- 컴파일러나 JIT는 특정 호출 시점에 객체의 타입이 명확할 경우, 가상 호출을 직접 호출(direct call)로 변경할 수 있습니다.
- 예를 들어, 최적화 과정에서 호출 대상이
final
클래스(더 이상 상속되어 오버라이드되지 않는 클래스)임이 확인되면, vtable 조회를 생략하고 직접 해당 함수를 호출할 수 있습니다.
- 이 과정은 인라인화(inlining) 등의 추가 최적화를 가능하게 하여 성능 향상에 기여합니다.
3. 가상 함수의 설계 및 응용
- 디자인 패턴에서의 활용:
- 템플릿 메서드 패턴(Template Method Pattern): 부모 클래스에서 기본 흐름을 정의하고, 가상 함수를 통해 특정 부분을 자식 클래스에서 구현하게 함으로써, 코드 재사용과 확장성을 높입니다.
- 전략 패턴(Strategy Pattern): 인터페이스(가상 함수 집합)를 통해 다양한 알고리즘을 캡슐화하고, 런타임에 교체할 수 있게 해줍니다.
- 메모리와 성능 고려:
- 가상 함수 사용 시, 각 객체에 vptr이 추가되어 메모리 오버헤드가 발생하지만, 이는 현대 컴파일러와 하드웨어에서 보통 무시할 수 있는 수준입니다.
- 성능이 중요한 경우, 가상 함수 호출의 오버헤드를 줄이기 위한 최적화(예: devirtualization)를 적극 활용합니다.