void main()
{
Human H("김사람");
Student S("이학생",1234567);
Human *pH;
Student *pS;
pH=&H; // 당연히 가능
pS=&S; // 당연히 가능
pH=&S; // 가능
// pS=&H; // 에러
pS=(Student *)&H;
pS->Intro();
}
자식은 부모 객체의 모든 것을 가지고 있기 때문에 부모 포인터는 자식을 가리킬 수 있지만 부모는 자식 객체의 모든 것을 가지고 있지 않기 때문에 자식 포인터는 부모를 가리킬 수 없다.
class Base
{
public:
void OutMessage() { printf("Base Class\n"); }
};
class Derived : public Base
{
public:
void OutMessage() { printf("Derived Class\n"); }
};
void main()
{
Base B,*pB;
Derived D;
pB=&B;
pB->OutMessage();
pB=&D;
pB->OutMessage();
}
위 코드는
Base Class
Base Class
메시지를 출력한다. 의도는 pB가 D의 번지를 가리킬 때 Derived Class를 출력하는 것이지만 이렇게 출력되는 이유는
컴파일러가 포인터의 정적 타입을 보고 타입에 맞는 멤버 함수를 호출하기 때문이다.

좌변의 포인터는 정적 타입이고 우변의 포인터는 동적 타입이다.
class Base
{
public:
virtual void OutMessage() { printf("Base Class\n"); }
};
class Derived : public Base
{
public:
virtual void OutMessage() { printf("Derived Class\n"); }
};
함수 앞에 virtual 키워드를 붙이면 가상 함수가 되는데, 가상 함수는 동적 타입을 따르므로
원래 의도대로 Base Class와 Derived Class를 출력하게 된다.

기존 함수는 링크 시점에서 이미 어디로 가는지 정해져 있는 정적 결합 이지만 가상함수는 pB가 가리키는 객체에 따라 호출할 함수가 달라지므로 동적 결합이다. 동적 결합은 멤버 함수를 포인터나 레퍼런스로 호출할 때만 작동하는데, 객체로 호출할 때는 호출 객체 타입을 알 수 있어서 작동하지 않는다.
가상 함수를 저장하기 위해서 컴파일러는 객체마다 vtable을 만들고 객체의 선두에 vtable의 번지인vptr을 저장한다.

그리고 각 객체의 가상함수들을 vtable에 저장한다. 파생 클래스에서는 재정의한 가상 함수는 새로 저장되고 재정의하지 않은 상속받은 가상함수들은 기반 클래스의 정의되어 있는 함수로 찾아간다.
void main()
{
// Derived D;
Base *pB;
pB=new Derived;
delete pB;
}
확장될 가능성이 있는 파괴자는 가상함수로 정의하는 것이 좋다. 생성자를 호출할 때는 어떤 타입인지 명시가 되있지만 파괴자는 정적 타입을 따라가기 때문에 위 코드와 같이 기반 클래스 포인터 pB가 파생클래스 D를 가리킬 때 pB를 삭제할 시 pB의 파괴자가 호출된다. 그래서 기반 클래스, 파생 클래스 파괴자에 모두 virtual 키워드를 추가하는 것이 좋다. 그러면 파생 클래스 파괴자가 먼저 호출되고 기반 클래스 파괴자가 호출된다.
'C++' 카테고리의 다른 글
| [C++] 함수 템플릿 (0) | 2022.01.06 |
|---|---|
| [C++] 순수 가상 함수 (0) | 2022.01.06 |
| [C++] 포함, private 상속, public 상속 (0) | 2022.01.05 |
| [C++] 다중 상속과 virtual 클래스 (0) | 2022.01.05 |
| [C++] 상속받은 멤버 초기화 (0) | 2022.01.05 |