처음 show() 호출은 파생 클래스 객체의 오버라이딩된 가상 함수를 호출하는 것이고, 두 번째 show() 호출은 기본 클래스의 가상 함수가 호출된 것이다.
파생 클래스에서 순수 가상 함수를 오버라이딩하지 않는다면 파생 클래스 또한 추상 클래스이다.
따라서 위와 같이 순수 가상 함수를 오버라이딩하면 구체 클래스가 되어 객체를 생성하고 런타임 다형성을 통해 오버라이딩한 함수가 호출될 수 있다.
가상 생성자와 같은 것은 없다. 생성자를 가상으로 만드는 것은 생성의 책임을 다른 객체에 위임한다는 의미인데 이는 의미가 없다.
가상 함수를 오버라이딩하는 파생 클래스 객체를 참조하는 포인터나 참조변수를 통해 해당 객체가 소멸된다면 이는 가상 소멸자가 호출되어야 한다. 가상 소멸자를 호출해야만 파생 클래스 객체의 자원까지 완벽하게 해제할 수 있다.
new Derived()
가 호출되면서 파생 클래스 생성자가 호출 -> 부모 클래스 생성자 호출 및 처리 -> 파생 클래스 생성자 처리delete Var
이 호출되면서 파생 클래스의 가상 소멸자 호출 및 처리 -> 부모 클래스 소멸자 호출 및 처리클래스 A(기본 클래스)는 VPTR이라는 가상함수테이블을 가리키는 포인터를 가진다. (클래스 B에는 없다.)
이 VPTR은 클래스 크기에 추가된다.
reference:
컴파일 시 가상함수가 정의된 클래스가 있다면, 이 클래스의 가상함수 테이블(v-table)을 만들어 'rdata' 영역에 기록한다.
그리고 특정 객체의 가상함수가 호출될 때에는 이 가상함수 테이블을 참조하여 함수가 호출된다(런타임시 결정).
class Parent{
virtual void func1(){
AAA
}
virtual void func2(){
BBB
}
virtual void func3(){
CCC
}
void func4(){
DDD
}
}
class Child : public Parent{
virtual void func1(){
childA
}
virtual void func3(){
childC
}
}
source: https://cosyp.tistory.com/228
객체가 생성되면 객체마다 공간이 생성된다. 이때 해당 공간에 각 객체이 함수 호출에서 참조해야할 가상테이블 주소가 해당 공간에 포함된다(객체의 공간에 첫 4바이트(또는 8바이트)에 존재).
위 그림에서 func1은 가상함수로써 부모클래스와 자식클래스가 참조해야할 함수 주소가 다르다는 것을 확인할 수 있다. 반면 func2는 가상함수이지만 오버라이딩되지 않은 함수임을 추측할 수 있다.
자식 클래스의 vtable은 부모 클래스의 vtable 값들이 그대로 복사되며, 다만 오버라이딩된 함수의 주소만 갱신된다.
가상함수 테이블에는 일반 멤버 함수에 대한 정보는 들어있지 않다.
런타임시 가상함수가 호출되면 호출된 가상함수가 어떤 객체로 부터 호출되었는지 파악하고, 이 객체의 vtable을 참조한다. 그리고 가상함수 종류에 따라 offset + vtable 첫번째 주소
를 통해 호출한다(동적 바인딩).
B::fun()은 virtual 키워드를 사용하지 않더라도 가상이다.
클래스에 가상 함수가 있으면 모든 자손 클래스에서 동일한 선언을 가진 함수는 자동으로 가상이 된다.
따라서 B와 C에서 fun() 선언에 virtual 키워드가 없더라도 가상 함수이다.
기본 클래스의 함수는 가상 함수를 포함하여 범위 확인 연산자(scope resolution operator, ::)를 통해 접근할 수 있다.