C++ Chapter 12.8 : 가상 상속으로 다이아몬드 상속 문제 해결

Date:     Updated:

카테고리:

태그:

인프런에 있는 홍정모 교수님의 홍정모의 따라 하며 배우는 C++ 강의를 듣고 정리한 필기입니다. 😀
🌜 [홍정모의 따라 하며 배우는 C++]강의 들으러 가기!


chapter 12. 가상 함수들 : 가상 상속으로 다이아몬드 상속 문제 해결

🔔 다이아몬드 상속 문제

image

  • B1, B2 클래스는 A 클래스를 상속받고 C 는 B1, B2 클래스를 둘 다 상속 받는다.(다중 상속)
  • 1 번 그림을 생각하며 C 에 A 타입의 메모리가 단 하나만 존재 할 것을 기대하지만 실제론 2 번 그림처럼 C 에 A 타입 메모리가 2 개가 생긴다.

다이아몬드 상속 문제 👉 다중 상속시, 다이아몬드 구조로 상속되는 것이 아닌 손자 객체에 할머니 객체가 2 개가 생기는 것.

#include <iostream>

using namespace std;

class PoweredDevice
{
public:
	int m_i;

	PoweredDevice(int power)
	{
		cout << "PoweredDevice: " << power << "\n";
	}
};

class Scanner : public PoweredDevice
{
public:
	Scanner(int scanner, int power)
		:PoweredDevice(power)
	{
		cout << "Scanner: " << scanner << "\n";
	}
};

class Printer : public PoweredDevice
{
public:
	Printer(int printer, int power)
		: PoweredDevice(power)
	{
		cout << "Printer: " << printer << "\n";
	}
};

class Copier : public Scanner, public Printer
{
public:
	Copier(int scanner, int printer, int power)
		: Scanner(scanner, power), Printer(printer, power)
	{}

};

int main()
{
	Copier cop(1, 2, 3); //생성자 호출

    cout << &cop.Scanner::PoweredDevice::m_i << endl;
	cout << &cop.Printer::PoweredDevice::m_i << endl;

	return 0;
}
💎출력💎

PoweredDevice : 3
Scanner : 1
Powereddevice : 3
Printer : 2
000000A95D0FF9EC
000000A95D0FF9E8
  • 할머니인 PoweredDevice의 생성자가 2 번 호출되는 것을 볼 수 잇다.
    • 즉 손자인 Copier타입의 객체 copPoweredDevice타입의 사본이 2 개가 들어있음을 알 수 있다.
  • m_iPoweredDevice로부터 상속 받은 멤버 변수인데 Scanner로부터 받은 m_iPrinter로부터 받은 m_i의 주소가 다른 것을 보아 동일한 하나의 PoweredDevice가 아닌 각각 다른 두개의 PoweredDevice타입 객체가 cop에 상속되었음을 알 수 있다.


🔔 virtual 가상 상속

virtual가상 상속 받으면 상속시 부모 타입의 메모리가 중복되지 않으면서 상속된다. 위 문제 해결!

  • 단, 다중 상속 하는 손자 클래스의 경우 할머니 클래스의 생성자를 꼭 직접 호출해주어야 한다.
#include <iostream>

using namespace std;

class PoweredDevice
{
public:
	int m_i;

	PoweredDevice(int power)
	{
		cout << "PoweredDevice: " << power << "\n";
	}
};

class Scanner : virtual public PoweredDevice
{
public:
	Scanner(int scanner, int power)
		:PoweredDevice(power)
	{
		cout << "Scanner: " << scanner << "\n";
	}
};

class Printer : virtual public PoweredDevice
{
public:
	Printer(int printer, int power)
		: PoweredDevice(power)
	{
		cout << "Printer: " << printer << "\n";
	}
};

class Copier : public Scanner, public Printer
{
public:
	Copier(int scanner, int printer, int power)
		: Scanner(scanner, power), Printer(printer, power), PoweredDevice(power)
	{}

};

int main()
{
	Copier cop(1, 2, 3); //생성자 호출

    cout << &cop.Scanner::PoweredDevice::m_i << endl;
	cout << &cop.Printer::PoweredDevice::m_i << endl;

	return 0;
}
💎출력💎

PoweredDevice : 3
Scanner : 1
Printer : 2
000000A95D0FF998
000000A95D0FF998
  • Scanner, PrintPoweredDevice를 가상으로 상속 받고 있다.
    class Scanner : virtual public PoweredDevice
    
    class Copier : public Scanner, public Printer
    
    • 이 덕분에 Scanner, Print을 둘 다 상속 받는 CopierPoweredDevice타입이 중복되지 않고 단 하나만 존재하게 된다.
      • 출력 결과 PoweredDevice타입 생성자가 딱 한번만 호출된 것을 볼 수 있다.
      • Scanner로부터 받은 m_iPrinter로부터 받은 m_i의 주소 또한 동일하다는 것을 알 수 있다.
  • Copier의 생성자에서 따로 직접 PoweredDevice타입 생성자를 호출해주어야 한다.
    • PoweredDevice을 가상 상속을 받은 후로 그의 자식인 ScannerPrinter더 위로 올라가지 않기 때문에 PoweredDevice생성자 호출를 직접 해주어야 한다.
      • 안해주면 컴파일러가 알아서 PoweredDevice의 디폴트 생성자를 호출하는데 현재 PoweredDevice 생성자 중 매개변수가 있는 생성자가 있으므로 이를 직접 호출해야 에러가 안난다.
        Copier(int scanner, int printer, int power)
          : Scanner(scanner, power), Printer(printer, power), PoweredDevice(power)
        {}
        
      • 어차피 가장 조상은 PoweredDevice이기 때문에 초기화 목록에서 제일 뒤에있어도 PoweredDevice가 가장 먼저 메모리에 정의 된다. 참고 포스트
      • 가상 상속의 원리
        • image


🌜 개인 공부 기록용 블로그입니다. 오류나 틀린 부분이 있을 경우 
언제든지 댓글 혹은 메일로 지적해주시면 감사하겠습니다! 😄

맨 위로 이동하기

댓글남기기