C++에 존재하는 상속 가능성을 의미하는 함수
C++에 존재하는 virtual Function은 일반 함수와 다르게 런타임에 실 객체 타입에 따라 호출되는 함수
#include <iostream>
class Animal {
public:
// 1. 가상 함수: 런타임에 실제 객체 타입에 따라 호출
virtual void makeSound() {
std::cout << "동물이 소리를 냅니다." << std::endl;
}
// 2. 일반 함수: 컴파일 타임에 포인터 타입(Animal) 기준으로 호출이 결정
void sayHello() {
std::cout << "동물이 인사합니다." << std::endl;
}
};
class Dog : public Animal {
public:
// makeSound()를 자식 클래스에 맞게 재정의(override)
void makeSound() override {
std::cout << "멍멍!" << std::endl;
}
// sayHello()도 재정의
void sayHello() {
std::cout << "강아지가 인사합니다." << std::endl;
}
};
int main() {
// 부모 클래스 포인터로 자식 클래스 객체를 가리킴
Animal* pAnimal = new Dog();
pAnimal->makeSound(); // 출력: "멍멍!" (O)
// virtual 함수이므로, pAnimal이 가리키는 실제 객체 Dog의 함수가 호출됨
pAnimal->sayHello(); // 출력: "동물이 인사합니다." (X)
// 일반 함수이므로, 포인터 타입인 Animal의 함수가 호출됨
delete pAnimal;
return 0;
}
virtual Function으로 선언된 함수를 override할 때는 해당 키워드를 안 쓰더라도 괜찮음. virtual 키워드도 동일. 실수 방지를 위해 명시
virtual 키워드로 선언하지 않은 메서드는 컴파일 타임에 선언된 포인터 타입을 기준으로 메서드 호출
Swift의 클래스는 C++처럼 virtual 키워드가 없어도 기본적으로 모든 메서드가 가상 함수처럼 동적 디스패치로 동작
상속이 불가능한 구조체(Struct)나 열거형(Enum)은 항상 정적 디스패치로 동작하여 성능상 이점
동적 디스패치에서 가상 함수를 호출할 때 어떤 함수를 호출할 지 알려주는 테이블
⚠️ Swift에서는 vTable이라는 개념은 없음. 동일한 메커니즘만 존재
컴파일러는 코드를 기계어로 번역할 때, 호출할 함수의 메모리 주소를 미리 알아야 함.
Animal* pAnimal = new Dog(); 와 같이 부모 타입 포인터가 자식 객체를 가리키는 경우, pAnimal->makeSound()가 Animal의 것인지 Dog의 것인지 컴파일 시점에는 절대 알 수 없음.
이를 위해 런타임에 호출할 함수를 결정해야 하는 동적 디스패치를 위해 vTable로 호출해야 할 함수를 알려주는 역할

C++에서 virtual 메서드를 지닌 클래스는 vTable을 가진다.
vTable은 해당 클래스의 virtual Function들의 실제 메모리 주소를 가진 배열
각 객체의 숨겨진 포인터 vPtr은 자신의 가상함수 주소를 가리키는 vTable의 주소를 가리킴.
자식 클래스가 가상 함수를 재정의하면 vTable에는 재정의된 가상 함수 주소를 가리키고, 재정의하지 않았다면 부모의 가상함수 주소를 가리킴.
동적 디스패치는 이를 이용해 런타임 시점에 호출되어야 할 함수를 찾음.
class Animal {
func speak() {
print("...")
}
}
class Dog: Animal {
override func speak() {
print("Woof!")
}
}
let pet: Animal = Dog()
// 런타임에 pet 객체의 테이블을 보고 Dog의 speak()를 호출
pet.speak() // 출력: "Woof!"
Swift는 키워드 없이 내부적으로 vTable과 같은 개념을 통해 동적 디스패치가 동작.

부모 자식의 수직적 관계로 이루어지는 테이블
객체 주소 → vTable 주소 → 함수 주소로 이어지는 2단계의 경로로 이어지므로, 여러 방식에 따라 상대적으로 성능 차이가 있음.
프로토콜로 다형성을 구현하기 위한 테이블

사용처: 클래스에만 적용되는 vTable, 클래스, 구조체, 열거형을 아우르는 Witness Table
생성 시점: 클래스 정의 시점에 생성되는 vTable, 프로토콜을 채택한 시점에 생성되는 Witness Table
구조: 수직적인 vTable, 수평적인 Witness Table
프로토콜의 요구사항 확인
해당 프로토콜이 채택된 구현부 찾기
채택된 각 타입과 프로토콜 조합으로 Witness Table 생성 및 실제 구현된 메서드의 주소를 기록
프로그램이 실행될 때, 프로토콜 타입의 변수는 Existential Container라는 보이지 않는 상자에 담겨 관리
Existential Container 대한 설명
https://velog.io/@js1436kt/Swift-Existential-Container
해당 변수의 Existential Container에 접근
Existential Container안에 존재하는 인스턴스를 가리키는 포인터와 타입과 프로토콜 조합으로 만든 Witness Table을 가리키는 포인터로 접근
해당 Witness Table을 찾아 호출할 메서드의 실제 주소를 가져와 실행
이때, 값 포인터는 실행될 메서드에서 사용될 데이터 제공
Existential Container를 위해 메모리 할당/관리, 위트니스 테이블을 조회하는 등 여러 포인터로 주소를 이동하면서 단계를 거치는 과정 때문에 오버헤드가 가장 큼.
참고
https://programmerpsy.tistory.com/68