강께르의 개발일지

[C++] 다중 상속과 가상 상속 본문

프로그래밍/C++

[C++] 다중 상속과 가상 상속

강께르 2021. 6. 16. 22:51

- C++은 다중 상속을 지원하는 언어이지만, 다중 상속이 논란이 많은 문법이므로, 가급적 사용하지 말아야 한다고 말한다.

- 하지만 라이브러리에 다중 상속을 적용한 예가 있기에 이를 이해하기 위해서 공부할 필요가 있다.

 

1. 다중 상속

- 다중 상속이란, 둘 이상의 클래스를 동시에 상속하는 것을 말한다.

class BaseOne
{
public:
	void FuncOne() { cout << "FuncOne()" << endl; }
};

class BaseTwo
{
public:
	void FuncTwo() { cout << "FuncTwo()" << endl; }
};

class MultiDerived : public BaseOne, public BaseTwo
{
public:
	void FuncMulti()
	{
		FuncOne();
		FuncTwo();
	}
};

- 위와 같은 코드에서 MultDerived 클래스가 다중 상속을 받은 예이다.

- BaseOne 클래스의 함수와 BaseTwo 클래스의 함수 모두를 사용할 수 있는 것이 다중 상속의 특징이다.

- 언뜻 보면 여러 클래스의 속성을 다중 상속으로 사용할 수 있어 좋지 않을까라는 생각을 하게 된다. 다음과 같은 상황은 어떨까?

 

2. 다중 상속의 문제점

- 다중 상속의 대상이 되는 두 부모 클래스가 동일한 이름의 멤버 변수나 함수가 있으면 어떻게 할 것인가? 위의 코드를 살짝만 바꿔보자.

class BaseOne
{
public:
	void Func() { cout << "FuncOne()" << endl; }
};

class BaseTwo
{
public:
	void Func() { cout << "FuncTwo()" << endl; }
};

class MultiDerived : public BaseOne, public BaseTwo
{
public:
	void FuncMulti()
	{
		Func(); // 이러면 무엇을 호출할 것인가?
		Func(); // BaseOne, BaseTwo 둘 다 같아서
			// 컴파일러는 모호하다고 이야기할 것이다.
		BaseOne::Func();
		BaseTwo::Func(); // 이와 같이 하면 될 것이다.
	}
};

- 컴파일러는 Func() 함수가 중복되는 상황에서 어느 클래스에 선언된 함수에 접근하라는 것인지 모르겠다고 할 것이다.

- 그에 대한 해결책으로 범위 지정 연산자를 사용해 어떤 클래스의 멤버인지 명시해주는 것이다.

 

- 이게 다중 상속의 문제의 끝이 아니다. 또 하나의 문제를 보자.

class Base
{
public:
	void FuncBase() { cout << "Func()" << endl; }
};

class BaseOne : public Base
{
public:
	void Func() { cout << "FuncOne()" << endl; }
};

class BaseTwo : public Base
{
public:
	void Func() { cout << "FuncTwo()" << endl; }
};

class MultiDerived : public BaseOne, public BaseTwo
{
public:
	void FuncMulti()
	{
		...
	}
};

- 최상위 부모 클래스가 하나 더 생겼다. 최상위 부모 클래스가 부모 클래스 두 개를 자식으로 하여 상속하고, 그 부모 클래스 두 개가 자식 클래스를 다중 상속을 하고 있다.

- 문제가 없을 것 같나? 멤버는 범위 지정 연산자로 지정하여 호출하면 되니까 상관없지 않은가? 하지만 아니다. 이 경우에는 최상위 부모 클래스의 멤버가 다중 상속받는 자식 클래스에게 어떻게 적용되는지 봐야 한다.

- 위의 그림은 구리긴 하지만, 여기서 중복되어 보이는 곳이 보이지 않은가? 최상위 부모 클래스인 Base 클래스의 멤버가 중복되어 상속받게 된 모습이다. 이럴 경우 BaseOne::FuncBase()을 호출하듯이 Base 클래스를 상속받은 그 클래스의 이름을 범위지정 연산자로 지정하여 Base의 멤버를 호출하면 되긴 하다.

- 중복 상속된 이 경우를 해결할 방안은 이것밖에 없는 것일까? 이 방안에서도 virtual 키워드가 등장한다.

 

3. 가상 상속

- 가상 상속을 쓰는 방법은 다중 상속을 통해 중복될 것이라고 판단될 클래스를 상속 받을 때, 접근 제어 지시자 앞에 virtual 키워드를 붙여준다. 코드와 함께 다시 보자.

class Base
{
public:
	void FuncBase() { cout << "Func()" << endl; }
};

class BaseOne : virtual public Base
{
public:
	void Func() { cout << "FuncOne()" << endl; }
};

class BaseTwo : virtual public Base
{
public:
	void Func() { cout << "FuncTwo()" << endl; }
};

class MultiDerived : public BaseOne, public BaseTwo
{
public:
	void FuncMulti()
	{
		...
	}
};

- 이렇게 상속하면 중복되어질 것이라고 판단된 클래스를 단 한 번만 상속하도록 될 것이다.

- 이를 도형으로 다시 표현하면 다음과 같을 것이다. 이런 모습일 것이라고 이해만 하면 될 것 같다.