다중 상속은 직계 인접한 기초 클래스를 하나 이상 가지는 클래스를 서술한다. 단일 상속과 마찬가지로, public 다중 상속은 is-a 관계를 나타내야한다.
class SingingWaiter : public Waiter, public Singer {...}; //O 둘다 public
class SingingWaiter : public Waiter, Singer {...}; //X Singer는 자동으로 private 상속이 된다.
다중 상속은 사용할 때에 조심하여야 한다. 다른 두 기초 클래스로부터 이름은 같지만 서로 다른 메서드들을 상속하는 문제, 둘 이상의 서로 관련된 인접 기초 클래스들로부터 어떤 클래스의 다중 인스턴스를 상속하는 문제이다.
하나의 기초 클래스에서 파생 클래스를 두개를 만들고 그것들을 다중 상속할 때에 문제가 생긴다.
SingingWaiter | ||
Singing | Waiter | |
Worker |
SingingWaiter ed;
Worker * pw = &ed; //모호하다.
Worker * pw1 = (Waiter *) &ed; //Waiter에 있는 Worker를 pw1으로
Worker * pw2 = (Singer *) &ed; //Singer에 있는 Worker를 pw2으로
여기서 ed는 두 개의 Worker 객체를 내포하고 있다. 그래서 선택할 수 있는 주소가 두 개이다. 강제 데이터형 변환을 사용하여 어느 객체를 말하는지 지정할 수 있을 것이다.
Singer Worker |
Waiter Worker |
각 객체에 하나 씩 Worker를 가지고 있어서 SingingWaiter는 총 두 개의 Worker객체를 상속한다.
다중 상속을 사용할 때, 동일한 기초 클래스(Base Class)를 여러 번 상속받게 되면, 기초 클래스의 복사본이 여러 개 생성될 수 있습니다. 이를 다이아몬드 상속 문제라고 합니다. 가상 기초 클래스는 이 문제를 해결하기 위해 사용됩니다.
가상 기초 클래스
class Singer : virtual public Worker {...};
class Waiter : public virtual Worker {...};
//public과 virtual의 순서는 상관없다.
class SingingWaiter : public Singer, public Waiter {...};
가상 기초 클래스는 하나의 공통 조상을 공유하는 여러개의 기초 클래스로부터 공통 조상의 유일 객체를 상속하는 방식으로 객체를 파생시키는 것을 허용한다. (기초 클래스를 하나만 만든다.)
- 가상 기초 클래스(Virtual Base Class)는 다중 상속 시 발생할 수 있는 문제를 해결하기 위해 사용되는 C++의 상속 기능입니다.
- 위와 같을 때 SingingWaiter에서 Worker객체의 복사본을 하나만 가진다.
Worker |
Singer |
Waiter |
SingingWaiter는 Worker객체를 하나만 상속한다.
가상 기초 클래스의 생성자 규칙
class A
{
public:
A(int n = 0) {a = n;}
private:
int a;
};
class B : public A
{
public:
B(int n = 0, int m = 0;) : A(n) {b = m;}
private:
int b;
};
class C : public B
{
public:
C(int p = 0,int n = 0, int m = 0;) : B(n, m) {c = p;}
private:
int c;
};
위와 같이 있을 때 파생 클래스는 자신이 상속한 클래스의 생성자만을 불러올 수 있다. C->B->A
이와 같은 정보의 자동 전달은 Worker가 가상 기초 클래스일 때에는 동작하지 않는다.
SingingWaiter(const Worker & wk, ...) : Waiter(wk, ...), Singer(wk, ...) { } //결함 있음
정보의 자동 전달에 의해 wk가 서로 다른 두 경로(Waiter와 Singer)를 거쳐서 Worker 객체에 전달된다는 것이다.
c++은 중간 클래스를 통해 기초 클래스로 전달되는 것을 정지시킨다. wk는 Waiter 종속 객체에 전달되지 않는다.
그러나 컴파일러는 파생 객체를 생성 전에 기초 객체 성분을 생성해야 된다. 이 경우에 디폴트 Worker생성자를 사용할 것이다.
- 가상 기초 클래스를 위해 디폴트 생성자를 사용하기 원하지 않는다면 기초 클래스의 생성자를 명시적으로 호출해야 한다.
SingingWaiter(const Worker & wk, ...) : Worker(wk), Waiter(wk, ...), Singer(wk, ...) { }
Worker(const Worker &)생성자를 명시적으로 호출한다. 이와 같은 것은 가상 기초 클래스에서 적절하다.
가상 메서드로 Show()를 기초 클래스 Worker에서 선언을 하고 Waiter와 Singer에서 정의를 했을 때 SingingWaiter에서는 Show()를 불러올시에 가장 가까운 조상의 정의를 가져오지만 이 경우에서는 Waiter와 Singer 둘 다 가지고 있기에 호출이 모호하게 된다.
같은 이름 함수의 모호함의 해결 방안
test1.Singer::Show();
- 사용범위 결정 연산자를 사용한다.
void SingerWaiter::Show()
{
Singer::Show();
}
- 사용할 버전을 재정의한다.
가상 기초 클래스와 가상이 아닌 기초 클래스의 혼합
class A
class B : virtual public A
class C : virtual public A
class D : public A
class E : public A
class F : public B, public C, public D, public E
위와 같이 클래스 A가 B,C에 대해서 가상 기초 클래스이고 D,E는 가상이 아닌 기초 클래스인 상황에서 F가 파생클래스들을 상속할 경우
A는 가상끼리의 객체와 D, E의 객체로 총 3개의 A 종속 객체를 내포하게 된다.
- 가상 기초 클래스를 사용하면, 기초 클래스의 복사본이 하나만 생성되어 중복 문제가 해결됩니다.
'C++ > Basic' 카테고리의 다른 글
[C++] 프렌드 클래스 (0) | 2021.07.22 |
---|---|
[C++] 템플릿 클래스 (0) | 2021.07.19 |
[C++] public 다형 상속 (0) | 2021.07.06 |
[C++] 클래스의 상속 (0) | 2021.07.05 |
[C++] 멤버 초기자 리스트 (0) | 2021.07.04 |