다중 상속

다중 상속 : 인터페이스가 아닌 다수의 클래스를 상속하는 기능

다이아몬드 상속

Diamond_Inheritance
공통된 부모를 가진 부모 클래스에서 자식 클래스가 상속받은 경우 다이아몬드 상속이라고 한다. 상속 관계에 의한 그래프를 그리면 좌측와 같은 다이아몬드 형태지만 실제 객체의 메모리는 우측과 같은 형태를 나타낸다.
이 때 Bottom에서 Top의 top_a에 접근할 때 어느 쪽의 Top에 접근해야할지 모호해진다.
여러개의 부모 클래스 중에 같은 이름의 변수나 함수가 존재할 경우도 문제가 발생한다. MiddleA에 Print라는 함수가 있고, MiddleB에도 Print라는 함수가 있을 때 Bottom에서 부모 클래스의 Print함수를 호출하고자 하면 MiddleA와 MiddleB 중 어느쪽을 사용해야하는지 모호해진다.

Diamond_Result
위 클래스의 생성자를 따라가보자. 생성자에서 가장 먼저 처리하는 것이 부모 클래스의 생성자를 호출하는 것이다. 다중상속에서의 생성자 호출순서는 상속할 때 앞에 기재했던 순서대로 호출된다. 추적하기 쉽도록 생성자에 클래스의 이름을 출력하도록했다.

  1. MiddleA에서 호출한 Top의 생성자 처리
  2. MiddleA의 생성자 처리
  3. MiddleB에서 호출한 Top의 생성자 처리
  4. MiddleB의 생성자 처리
  5. Bottom의 생성자 처리
void PlusA()
{
	std::cout << ++MiddleA::Top::top_a << std::endl;
	std::cout << ++MiddleB::Top::top_a << std::endl;
}

결국 Top이 2번 생성되었다. Top이 1번 생성되고 MiddleA와 MiddleB가 같은 Top을 가리킨다면 Bottom의 멤버 함수인 위 코드를 실행했을 때 2가 출력되어야하지만, 실제로는 1만 출력되었다.

다이아몬드 상속의 해결방법

다중 상속을 하지않는다.

가장 좋은 해결방법이라고 생각한다. 문제가 발생할 여지가 적고, C#, JAVA등 많은 언어들은 다중 상속을 지원하지 않아 다이아몬드 상속이 일어나지 않음에도 불구하고, 완성된 프로그램을 개발할 수 있다.

명시적으로 지정한다.

위의 예제 코드와 같이 부모중 하나를 경유해서 접근한다. 모호성만 해결할 수 있어, 메모리 낭비가 존재한다.

가상 상속

부모 클래스에서 virtual 키워드로 조상 클래스를 상속받는다. 이러면 조상 클래스는 가상 기저 클래스가 되며 생성자는 1회만 호출된다. 하지만 부모 클래스 각각에 가상 기저 클래스의 멤버에 대한 오프셋이 들어있는 테이블의 포인터가 생성된다. 간단한 클래스의 경우 메모리가 증가할 수 있다.
일반적으로 이 테이블의 포인터는 가상 함수 테이블의 포인터 바로 뒤에 위치한다. 따라서 가상 기저 테이블에는 자신의 객체에 대한 오프셋 또한 들어있다.