자 마흔 두 번째 키워드인 '가상함수 테이블'을 알아 볼 것이다.
이 키워드는 C++에서 주로 사용되는 개념이므로 C++의 예시코드와
게임분야에서 사용할 수 있는 부분으로 알아보았다.

가상 함수 테이블(Virtual Function Table, vtable)은 객체 지향 프로그래밍에서 다형성을 구현하기 위해 사용되는 메커니즘 중 하나이다.
주로 C++와 같은 언어에서 사용되고, vtable의 개념을 이해하기 위해서는 먼저 가상 함수(virtual function)와 다형성(polymorphism)에 대해 알아야 한다.
가상함수
다형성
구조: 각 클래스에 대해 가상 함수의 주소를 저장하는 테이블.
객체의 vptr: 각 객체는 자신의 클래스의 vtable을 가리키는 포인터(vptr)를 가지고 있다.
객체 생성 시 vptr 초기화: 객체가 생성될 때, 해당 객체는 자신의 클래스의 vtable을 가리키는 vptr을 초기화한다.
가상 함수 호출 시 vtable 참조: 가상 함수를 호출할 때, 객체의 vptr을 통해 해당 vtable에 접근하여 올바른 함수 포인터를 찾고 호출한다.
#include <stdio.h> // C st
#include <string> //C++ style - STL
using namespace std;
class Character // 순수가상 함수를 가지고 있기 때문에 추상클래스임
{
public:
Character(int id, string name) {
this->id = id;
this->name = name;
printf("캐릭터 생성 %p - %s\n", this, this->name.c_str());
}
virtual ~Character() {
printf("캐릭터 소멸 %p - %s\n", this, this->name.c_str());
}
virtual void Move() {
printf("캐릭터 이동 %p - %s\n", this, this->name.c_str());
}
virtual void Attack() = 0; // 순수 가상함수
protected:
int id;
string name;
};
class Player : public Character {
public:
Player(int id, string name) : Character(id, name) {
printf("플레이어 생성 %p - %s\n", this, this->name.c_str());
}
~Player() override{
printf("플레이어 소멸 %p - %s\n", this, this->name.c_str());
}
void Move() override{
Character::Move();
printf("플레이어 이동 %p - %s\n", this, this->name.c_str());
}
void Attack() override {
printf("플레이어 공격 %p - %s\n", this, this->name.c_str());
}
};
int main()
{
Character* player3 = new Player(3, "Player3");
player3->Move();
player3->Attack();
delete player3;
return 0;
}

위 코드를 실행한 결과이다.
Character를 생성하고 가상함수를 정의해준 뒤 Player 클래스에서 상속을 진행했다.
Main 함수에서 포인터로 업캐스팅을 진행하고 Move와 Attack을 실행하고 소멸시킨 결과이다.
유연성: 동일한 인터페이스로 서로 다른 객체를 처리할 수 있어 코드의 유연성이 증가한다.
확장성: 새로운 클래스가 추가될 때 기존 코드를 수정할 필요 없이 새로운 클래스만 정의하면 된다.
코드 재사용성: 기본 클래스에서 정의한 인터페이스를 재사용할 수 있어 코드 중복을 줄일 수 있다.
런타임 다형성: 컴파일 시점이 아니라 실행 시점에 호출할 함수를 결정할 수 있어 동적 행동이 가능하다.
성능 오버헤드: 가상 함수 호출 시 vtable을 참조하는 과정에서 약간의 오버헤드가
발생할 수 있다. 특히, 게임처럼 성능이 중요한 애플리케이션에서는 주의해야 한다.
복잡성 증가: 다형성과 vtable을 사용하는 구조는 코드의 복잡성을 증가시킬 수 있다. 잘못된 사용이나 이해 부족으로 인해 버그가 발생할 수 있다.
메모리 사용 증가: 각 클래스마다 vtable이 필요하고, 각 객체마다 vptr이 필요하기 때문에 메모리 사용량이 증가할 수 있다.
디버깅 어려움: 다형성과 가상 함수를 사용하면 디버깅이 어려울 수 있다. 어떤 클래스의 메서드가 호출되는지 추적하기 어려울 수 있다.
가상함수를 참조하면서 키와 값으로 쌍을 이루기때문에 테이블이 만들어짐 {Virtual function Pointer}
가상함수 테이블 정적할당일때 부모한테 들어감
가상함수 테이블 동적할당일때 가상화가 이루어 졌는지(vfptr이 있는지)확인 후 맞으면 포워딩이 들어감
- 그래서 C에선 동적할당일때만 가상화를 한다.
가상화가 진행이 되면 소멸자도 가상화가 진행이 되어야 한다
순수가상함수를 하나라도 포함하는 클래스라면 추상클래스가 된다. (인터페이스 역할진행)
다형성
가상화를 통해 동일한 인터페이스로 다양한 구현체를 사용할 수 있게 된다. 이는 객체 지향 프로그래밍의 핵심 개념 중 하나이다. 가상 함수를 사용하면, 기반 클래스의 포인터나 참조자를 통해 파생 클래스의 함수를 호출할 수 있다. 이를 통해 코드의 유연성과 확장성이 높아진다.
코드 재사용성
가상화는 기본 클래스에서 정의된 인터페이스를 파생 클래스들이 공유하면서도 자신만의 구현을 제공할 수 있게 한다. 이를 통해 코드의 중복을 줄이고, 공통된 기능은 기본 클래스에 두고, 세부 구현은 파생 클래스에서 처리하게 할 수 있다.
유지보수의 용이성
가상화를 사용하면 시스템을 구성하는 각 컴포넌트를 독립적으로 변경할 수 있다. 기본 클래스와 파생 클래스의 관계를 통해, 특정 기능을 수정하거나 확장할 때 다른 부분에 영향을 최소화할 수 있다. 이는 시스템의 유지보수성을 높이는 데 큰 도움이 된다고 한다.
인터페이스의 설계
가상 함수와 추상 클래스를 사용하면 명확한 인터페이스를 설계할 수 있다. 추상 클래스는 인터페이스 역할을 하며, 이를 상속받는 클래스는 반드시 해당 인터페이스를 구현해야 한다. 이는 인터페이스의 일관성을 유지하고, 시스템의 각 부분이 예상대로 동작하도록 보장한다.
확장
가상화는 새로운 파생 클래스를 추가할 때 기존 코드를 변경하지 않고도 확장이 가능하게 한다. 이는 시스템의 확장성을 높여주며, 새로운 요구사항이 생길 때 유연하게 대처할 수 있다.
vtable : 가상함수를 포함하고 있는 모든 클래스에서 생성됨.
가장 가까운 결과만이 저장됨
_vfptr = void** 주소에 대한 배열
가상화 사용 이유 => Character char[3] = {Player, Monster, Boss};
Player, Monster, Boss → Character
Character.Move()가 있으면
char[0].Move == Player.Move() 여야 한다.
부모 클래스의 형식으로 콜해도 자식의 함수 실행하기 위해 가상화를 사용
정적할당으로 진행하면 가상화가 이루어 지지 않음
Ex) '부모타입.부모함수' 라면 가상화가 없음 / '부모타입.자식함수' 라면 가상화가 있음
가상화 하고 동적할당 하면 부모와 자식 모두에 같은 주소(자식함수 주소)를 가르키는 vtable이 생김
실제 가상 테이블은 2개로 보이지만 인스턴스당 하나만 생성
C++ 은 그냥 이제 처음에 접한 언어인 C언어와 비슷해서 친근하게 느껴졌는데 이번 기회로 확실히
어렵다는 언어라고 자각을 제대로 했다....ㅎㅎ
많은 공부가 필요한 분야인 만큼 제대로 알고 간 것 같아서 기분이 좋다.