Intro
가상 함수와 가상 함수 테이블에 대해 자세히 알아보자
1. 가상 함수

- 위와 같이 부모 클래스를 가리키는 포인터 p에 자식 클래스의 객체를 생성하였다.
따라서 포인터 p는 객체 TestB를 가리키고 있다. 몸체는 부모 클래스지만 실제 객체는 자식 클래스인 상태인 것이다.
그렇다면 이 포인터 p를 이용하여 오버라이딩된 멤버함수 TestFunc()를 호출하면 어느 클래스의 멤버함수가 호출이 될까? 정답은 아래와 같다.TestA::TestFunc() - 분명 자식 클래스의 객체를 선언하였음에도 불구하고 부모 클래스의 멤버함수가 호출되었다.
- 만약 여기서 가상 함수를 사용하면 하나의 객체가 여러 가지 타입을 가지고서 오버라이딩된 멤버 함수를 현재 가진 타입에 맞게 호출할 수 있다.
- 가상 함수는 자식 클래스에서 재정의할 것으로 기대하는 멤버 함수를 의미하는 것이다.
- C++에서 가상 함수는 virtual 키워드를 사용하여 선언할 수 있으며,
부모 클래스에서 virtual 키워드를 사용해 가상 함수를 선언하면 자식 클래스에서 재 정의된 멤버 함수도 자동으로 가상 함수가 된다. - 여기서 의문점이 하나 생긴다.
컴파일러는 컴파일 시점에 호출할 함수의 정확한 메모리 위치를 알아야 하는데, 가상 함수를 가진 포인터 객체의 타입은 런타임 시점에 언제든지 바뀔 수 있으므로 호출해야할 함수의 메모리 주소가 가변적이다. 컴파일러는 이를 어떻게 처리하는 것일까? - 이는 가상 함수 테이블에 대한 이해가 필요하다.
2. 가상 함수 테이블

- 먼저 클래스 위 화면의 TestA를 보면 virtual로 선언된 가상 함수가 존재한다.
- 이렇게 한개 이상의 가상 함수를 포함하는 클래스에 대해서 C++ 컴파일러는 각각의 객체마다 가상 함수 테이블을 가리키는 포인터를 저장하기 위한 숨겨진 멤버를 하나씩 추가한다.
- 이와 함께 컴파일러는 가상 함수를 단 하나라도 가지는 클래스에 대해서 아래 예시 형태와 같이 가상 함수 테이블을 작성한다.
[클래스 TestA의 가상 함수 테이블]
Function |
Address |
|---|---|
| void TestA::TestFunc1() | 0x1024번지 |
| void TestA::TestFunc2() | 0x1028번지 |
[클래스 TestB의 가상 함수 테이블]
Function |
Address |
|---|---|
| void TestB::TestFunc1() | 0x1032번지 |
| void TestA::TestFunc2() | 0x1028번지 |
- TestA 객체를 생성하면 객체에 TestA 클래스의 가상 함수 테이블 주소 값이 저장되고,
TestB 객체를 생성하면 객체에 TestB 클래스의 가상 함수 테이블 주소 값이 저장된다. - 실제 함수 호출 시 참조 과정 예시
TestB 객체의 TestFunc1 함수 호출 -> TestB 객체의 가상 함수 테이블 주소 참조 -> 0x1032번지 참조 - 마지막으로 메모리 뷰를 분석해보자

- 위 화면과 같이 생성된 객체에 타입에 맞는 가상 함수 테이블 주소 값이 저장된 것을 확인할 수 있다.
또한 가상 함수 테이블 주소로 이동하면 클래스에 맞는 가상 함수 테이블 표가 각각 존재하는 것을 확인할 수 있다. - [TIP] C++ 가상 함수의 경우 가상 함수 테이블의 존재로 인해 C에 비해 실행 속도가 더 느리지만, 다형성이라는 장점을 제공하기에 아주 유용하게 활용되고 있다.