본문으로 바로가기

Multiple Inheritance

지금 까지는 한 부모로부터만 상속받는 단일 상속을 알아보았다. 하지만 다음과 같은 상황을 생각해볼 수 있다.

위 그림처럼 Lion 클래스와 Tiger 클래스 모두에게서 상속받아서 Liger 클래스를 만들 수 있다. 이때 발생할 수 있는 몇 가지 특징을 살펴보자.

class Lion
{
public:
    virtual ~Lion() = default;
private:
    double LionData;
};
class Tiger
{
public:
    virtual ~Tiger() = default;
private:
    double TigerData;
};
class Liger : public Lion, public Tiger {
public:

private:
    double LigerData;
};

생성자, 소멸자 호출 순서

생성자 호출 순서는 다중 상속시 먼저 적혀있는 부모 클래스부터 호출된다. 소멸자는 그 반대의 순서대로 호출된다.

class Lion
{
public:
    Lion() { std::cout << "Lion()" << std::endl; }
    virtual ~Lion() { std::cout << "~Lion()" << std::endl; }
private:
    double LionData;
};
class Tiger
{
public:
    Tiger() { std::cout << "Tiger()" << std::endl; }

    virtual ~Tiger() { std::cout << "~Tiger()" << std::endl; }
private:
    double TigerData;
};
class Liger : public Lion, public Tiger {
public:
    Liger() { std::cout << "Liger()" << std::endl; }
    ~Liger() { std::cout << "~Liger()" << std::endl; }
private:
    double LigerData;
};
int main()
{
    Liger liger;
    return 0;
}
Lion()
Tiger()
Liger()
~Liger()
~Tiger()
~Lion()

가상 함수 테이블(vftable)과 가상 함수 포인터(vfptr)

위 클래스들에서 Lion, Tiger, Liger 클래스의 크기를 출력해보면 다음과 같다.

// Lion, Tiger, Liger 클래스 생략
int main()
{
    std::cout << "Lion size: " << sizeof(Lion) << std::endl;
    std::cout << "Tiger size: " << sizeof(Tiger) << std::endl;
    std::cout << "Liger size: " << sizeof(Liger) << std::endl;
    return 0;
}
Lion size: 16
Tiger size: 16
Liger size: 40

다중 상속 받은 Liger의 객체 크기가 상당히 크다는 것을 볼 수 있다. Liger 클래스에는 가상 함수 포인터가 다음과 같이 생성되기 때문이다.

위 그림처럼 Liger 객체는 Lion으로 부터는 Lion의 가상 함수 포인터, LionData 변수를 상속받고, Tiger으로 부터는 Tiger의 가상 함수 포인터, TigerData 변수를 상속받는다. 그리고 자신의 Liger 데이터까지 총 40바이트의 크기가 되는 것이다.

Liger는 Lion과 Tiger로부터 모두 가상함수 포인터를 상속받기 때문에 다음과 같이 "Liger 객체를 Lion 포인터에 생성할 경우"와 "Tiger 포인터에 생성할 경우" 모두 정상적으로 Liger의 Speak 함수가 호출될 수 있는 것이다.

class Lion
{
public:
    virtual void Speak() { std::cout << "Lion!" << std::endl; }
    virtual ~Lion() = default;
private:
    double LionData;
};
class Tiger
{
public:
    virtual void Speak() { std::cout << "Tiger!" << std::endl; }
    virtual ~Tiger() = default;
private:
    double TigerData;
};
class Liger : public Lion, public Tiger {
public:
    void Speak() override { std::cout << "Liger!" << std::endl; }
    ~Liger() = default;
private:
    double LigerData;
};
int main()
{
    Tiger* polyTiger = new Liger();
    Lion* polyLion = new Liger();

    polyTiger->Speak();
    polyLion->Speak();

    delete polyTiger;
    delete polyLion;

    return 0;
}
Liger!
Liger!

다이아몬드 상속(Diamond Inheritance)

Tiger 클래스와 Lion 클래스가 Animal 클래스로 부터 상속받아서 만들어졌을 경우 아래와 같은 모양이 된다. 이런 경우를 다이아몬드 상속이라고 한다.

다이아몬드 상속이 되면 최초 조상클래스인 Animal 클래스가 두 번 생성되는 문제가 발생한다. 아래의 코드르 살펴보자.

class Animal 
{
public:
    Animal() { std::cout << "Animal()" << std::endl; }
    virtual ~Animal() { std::cout << "~Animal()" << std::endl; }
};

class Lion : public Animal
{
public:
    Lion() { std::cout << "Lion()" << std::endl; }
    virtual ~Lion() { std::cout << "~Lion()" << std::endl; }
};
class Tiger : public Animal
{
public:
    Tiger() { std::cout << "Tiger()" << std::endl; }
    virtual ~Tiger() { std::cout << "~Tiger()" << std::endl; }
};
class Liger : public Lion, public Tiger {
public:
    Liger() { std::cout << "Liger()" << std::endl; }
    ~Liger() { std::cout << "~Liger()" << std::endl; }
};
int main()
{
    Liger liger;
    return 0;
}
Animal()
Lion()
Animal()
Tiger()
Liger()
~Liger()
~Tiger()
~Animal()
~Lion()
~Animal()

출력 결과에서 Animal이 두번 생성되고 소멸됨을 확인할 수 있다. 이를 방지하기 위해서는 virtual 상속이 필요하다. virtual 상속은 다음 글에서 다룬다.

 


출처: https://www.youtube.com/channel/UCHcG02L6TSS-StkSbqVy6Fg