우선 제가 멤버함수와 가상함수에 대해서 이해한 부분은 다음과 같습니다.
1. 일단 기본적으로 일반 멤버함수에 대해서
함수 Func1을 일반 멤버함수로 갖는 클래스 Base와,
Base를 상속하며, Func1을 오버라이딩한 클래스 Derived이 있다고 할 때,
Base::Func1 과 Derived::Func1 이 코드세그먼트에 먼저 올라간뒤,
Base와 Derived 객체들이 Func1에 접근할 때, 함수포인터 형태로 해당 함수에 접근한다고 알고 있습니다.
이때, Base의 포인터에 Derived 객체를 넣은 뒤 Func1을 호출하면 Derived::Func1이 아닌 Base::Func1이 호출된다고도 알고있습니다.
이를 봤을 때, 제 생각에는,
컴파일러는 각 객체 내부에 맴버변수로 Base::Func1 또는 Derived::Func1 의 함수포인터를 넣어둔 뒤 호출하는 방식으로 멤버함수 호출을 구현하는 것이 아니라, 컴파일 타임에서 포인터의 형태에 맞는 Func1 주소를 인스트럭션으로 삽입하는 형태로 구현하는 것이라고 생각했습니다.
그래야만, 실제 동작이 그러하듯, 객체의 실제 클래스가 아니라 포인터의 자료형에 따른 함수 호출이 가능할테니까요.
2. 가상함수에 대해서
가상함수 테이블을 컴파일러가 생성하고, 이를 데이터세그먼트에 올린 다음, 각 가상함수 테이블을 객체의 멤버변수 형태로 보관하고 있다가
객체가 가상함수에 접근할 때, 가상함수 테이블로 점프한 뒤 테이블을 참조하여 적절한 함수를 호출한다고 알고있습니다.
각 각의 방식에 대해서 떼어놓고 본다면 아무런 의문도 생기지 않습니다. 그런데 이 둘을 합치려니까 의문점이 생깁니다.
첫 째로, 일반 멤버함수의 형태와 가상함수의 형태로 나눌 필요가 있느냐 하는것입니다. 만일 제 생각대로 컴파일러가 1과 2의 구현을 다른 식으로 한다면, 컴파일 시간만 더 잡아먹는 것 아닌가요? 차라리 일반 멤버함수, 가상함수 구분없이 각 클래스에 대한 함수 테이블을 만든 뒤에 각 객체가 테이블 주소를 메타데이터로 갖고있으면,
"객체->함수테이블->함수" 형태로 구현을 일원화 할 수 있는것 아닌가요?
구현이 이원화 되어있으면 컴파일러는 클래스에 가상함수가 있는지 없는지를 먼저 판단하고 호출방식도 달리해야하는데 이는 상당히 비효율적이지 않나요?
둘 째로, 만약 제 생각이 틀리고, 1과 2의 구현이 같은 방식이라면, 어째서 Base 포인터에 Derived 객체를 넣은 뒤 Func1을 호출할 때 컴파일러는 실제 객체의 타입이 아니라, 포인터의 타입을 기준으로 함수호출을 판단하는 것인가요?