[문과 코린이의 IT 기록장] C,C++ - 가상(Virtual)의 원리와 다중상속 2 : 다중상속에 대한 이해 ( 다중상속에 대한 견해, 다중상속의 기본방법, 다중상속의 모호성, 가상 상속 )
[문과 코린이의 IT 기록장] C,C++ - 가상(Virtual)의 원리와 다중상속 2 : 다중상속에 대한 이해
( 다중상속에 대한 견해, 다중상속의 기본방법, 다중상속의 모호성, 가상 상속 )
1. 다중상속에 대한 견해
- 다중상속이란 둘 이상의 클래스를 동시에 상속한다는 것을 말한다.
- 그러나, 다중상속은 꽤 논란이 발생하는 문법이다. 따라서, 다중상속을 매우 예외적으로, 제한적으로 사용할 필요가 있다.
2. 다중상속의 기본방법
ex )
#include <iostream>
using namespace std;
class BaseOne{
public:
void SimpleFuncOne() { cout<<"BaseOne"<<endl; }
};
class BaseTwo{
public:
void SimpleFunctwo() { cout<<"BaseTwo"<<endl; }
};
class MultiDerived : public BaseOne, protected BaseTwo{
// 이렇듯, 쉼표(,)를 이용해서 상속의 대상이 되는 클래스를 구분하여 명시할 수 있다.
// 기초 클래스를 상속하는 형태는 각각 별도로 지정이 가능하다.
public:
void ComplexFunc(){
// 다중상속을 통해, BaseOne 클래스의 맴버함수와, BaseTwo 클래스의 맴버함수를 각각 호출하고 있다.
SimpleFuncOne();
SimpleFunctwo();
}
};
int main(){
MultiDerived mdr;
mdr.ComplexFunc();
return 0;
}
3. 다중상속의 모호성
- 다중상속의 대상이 되는 두 기초 클래스에, 동일한 이름의 맴버가 존재하는 경우 문제가 발생할 수 있다. 이 경우에는, 유도 클래스 내에서 맴버의 이름만으로 접근이 불가능하기 때문이다.
ex )
#include <iostream>
using namespace std;
class BaseOne{
public:
void SimpleFunc() { cout<<"BaseOne"<<endl; }
};
class BaseTwo{
public:
void SimpleFunc() { cout<<"BaseTwo"<<endl; }
};
class MultiDerived : public BaseOne, protected BaseTwo{
public:
void ComplexFunc(){
BaseOne :: SimpleFunc();
BaseTwo :: SimpleFunc();
// BaseOne 클래스, BaseTwo 클래스, 모두 SimpleFunc() 맴버함수가 존재하기 때문에, 이 둘을 상속하는 유도 클래스에서 SimpleFunc() 맴버함수를 호출할 경우에는, 어느 클래스에 정의된 함수를 호출해야 하는지 명시해야 한다.
}
};
int main(){
MultiDerived mdr;
mdr.ComplexFunc();
return 0;
}
4. 가상 상속
- 함수 호출관계의 모호함은 다른 상황에서도 발생할 수 있다.
ex )
#include <iostream>
using namespace std;
class Base{
public :
Base() { cout<<"Base Constructor"<<endl; }
void SimpleFunc() { cout<<"BaseOne"<<endl; }
};
class MiddleDerivedOne : virtual public Base{ // 가상 상속
public:
MiddleDerivedOne() : Base(){ // 기초 클래스의 생성자 호출을 명시하지 않아도 되지만, 호출 여부에 대해 알아보기 위해 명시함.
cout<<"MiddleDerivedOne Constructor"<<endl;
}
void MiddleFuncOne(){
SimpleFunc();
cout<<"MiddleDerivedOne"<<endl;
}
};
class MiddleDerivedTwo : virtual public Base{ // 가상 상속
public:
MiddleDerivedTwo() : Base(){ // 기초 클래스의 생성자 호출을 명시하지 않아도 되지만, 호출 여부에 대해 알아보기 위해 명시함.
cout<<"MiddleDerivedTwo Constructor"<<endl;
}
void MiddleFuncTwo(){
SimpleFunc();
cout<<"MiddleDerivedTwo"<<endl;
}
};
class LastDerived : public MiddleDerivedOne, public MiddleDerivedTwo{
public :
LastDerived() : MiddleDerivedOne(), MiddleDerivedTwo(){
// 가상상속(virtual 선언) 때문에, Base 클래스의 생성자가 1번만 호출되는 것을 볼 수 있다.
// 기초 클래스의 생성자 호출을 명시하지 않아도 되지만, 호출 여부에 대해 알아보기 위해 명시함.
cout<<"LastDerived Constructor"<<endl;
}
void ComplexFunc(){
MiddleFuncOne();
MiddleFuncTwo();
SimpleFunc();
}
};
int main(){
cout<<"객체생성 전 ...."<<endl;
LastDerived ldr;
cout<<"객체생성 후 ...."<<endl;
ldr.ComplexFunc();
return 0;
}
a. 가상상속 X
- 만약 virtual 선언을 하지 않은 상태에서, 객체가 생성될 경우, 아래와 같은 형태가 된다.
* Base 클래스의 맴버가 두 번 포함됨
[ 상속의 구조 ]
[ Base 클래스를 두 번 상속하는 LastDerived 클래스의 객체 ]
- 이와 같이 가상상속을 하지 않을 경우, 하나의 객체 내에서 두 개의 Base 클래스 맴버가 존재하기 때문에, ComplexFunc() 함수 내에서 이름만 가지고 SimpleFunc()함수를 호출할 수는 없다.
- 따라서, 어떤 클래스를 통해 간접 상속한 Base() 클래스의 맴버함수를 호출할 것인지 명시해야 한다.
ex )
MiddleDerivedOne :: SimpleFunc(); // MiddleDerivedOne 클래스가 상속한, Base 클래스의 SimpleFunc() 함수호출 명령
MiddleDerivedTwo :: SimpleFunc(); // MiddleDerivedTwo 클래스가 상속한, Base 클래스의 SimpleFunc() 함수호출 명령
- 사실, Base 클래스의 맴버는 LastDerived 객체에 하나씩만 존재하게 하는 것이 좋다. 따라서, 이를 해결하기 위해서 '가상 상속' 사용이 필요하다.
b. 가상 상속 O
ex )
class MiddleDerivedOne : virtual public Base { .... };
class MiddleDerivedTwo : virtual public Base { .... };
class LastDerived : public MiddleDerivedOne , public MiddleDerivedTwo { .... };
- 이와 같이 가상으로 상속된 두 클래스를, 다중 상속하게 되면, LastDerived 객체 내부에는, MiddleDerivedOne 클래스와 MiddleDerivedTwo 클래스가 동시에 상속하는 Base 클래스 맴버가 하나씩만 존재하게 된다.
- 따라서, 예제에서 문제 없이 SimpleFunc() 함수를 이름만 가지고 호출할 수 있다.
* 유의사항 - 아직 공부하고 있는 문과생 코린이가, 정리해서 남겨놓은 정리 및 필기노트입니다. - 정확하지 않거나, 틀린 점이 있을 수 있으니, 유의해서 봐주시면 감사하겠습니다. - 혹시 잘못된 점을 발견하셨다면, 댓글로 친절하게 남겨주시면 감사하겠습니다 :) |