오늘은 C++의 꽃이라 불리는 다형성에 대해 깊이 파고들었다. 모의 면접을 통해 내가 놓치고 있던 중요한 디테일들을 발견했다. 특히 가상 함수 테이블(vtable)이 언제 생성되고, 사용하는 데 어떤 비용이 드는지 정확히 알게 되었다. 단순히 '어떻게 쓰는가'를 넘어 '어떻게 동작하는가'를 이해하는 것이 얼마나 중요한지 깨달은 하루였다.
이전까지는 '부모 포인터로 자식 객체를 가리키면 알아서 자식의 오버라이딩된 함수가 호출된다' 정도로만 알고 있었다. 오늘 그 '알아서'의 과정을 명확히 정리했다.
Animal* p = new Dog(); 와 같이 기반 클래스 포인터가 파생 클래스 객체를 가리킨다.Dog 객체 내부에는 가상 함수 테이블 포인터(vptr)가 숨겨져 있다.vptr은 Dog 클래스의 가상 함수 테이블(vtable)을 가리킨다.p->speak(); 가 호출되면, 프로그램은 런타임에 p가 가리키는 객체의 vptr을 따라간다.vptr이 가리키는 Dog의 vtable에 가서 speak() 함수의 실제 주소를 찾는다.Dog 클래스에 오버라이딩된 speak() 함수가 호출된다. ✨이 부분에서 내가 가장 많이 착각하고 있었다.
vptr)만 멤버로 가질 뿐이다. 모든 Dog 객체는 똑같은 Dog 클래스의 vtable을 가리킨다.vptr을 통해 vtable을 거쳐 함수 주소를 찾는, 즉 한 단계 더 거쳐서(간접 참조) 호출하므로 아주 약간의 실행 시간 오버헤드가 발생한다.vptr을 가져야 하므로, 객체의 크기가 포인터 크기만큼(보통 8바이트) 커진다.가상 함수 테이블은 런타임에 객체별로 생성된다고 착각했다.
vptr)만 갖는다.가상 함수 사용의 단점을 컴파일 시간 증가라고 잘못 생각했다.
vptr이 추가되어 메모리 사용량이 늘어난다.| 개념 | 설명 | 비고 |
|---|---|---|
| 동적 바인딩 (과정) | 객체의 vptr → 클래스의 vtable → 실제 함수 주소 순서로 런타임에 호출할 함수를 결정한다. | vptr: virtual pointer vtable: virtual table |
| 가상 함수 테이블 (생성) | 컴파일 타임에 가상 함수를 가진 클래스별로 1개 생성된다. | 객체별로 생성되는 것이 절대 아님! |
| 가상 함수 (비용) | 1. 런타임 오버헤드: 간접 호출로 인한 약간의 성능 저하 2. 메모리 오버헤드: 객체 크기 증가 ( vptr 만큼) | 컴파일 시간과는 거의 무관하다. |