1. 다중상속의 문제점
개발자들 사이에서 다중상속에 관해 크게 2가지 견해로 갈린다.
1. 단일상속이 좋다면, 다중상속은 더 좋다.
2. 단이상속은 좋지만, 다중상속은 골칫덩어리다.
'다중 상속'하면 바로 우리 머리에 들어와야할 사실은 둘이상의 기본 클래스로부터 똑같은 이름을 물려받을 가능성이 생긴다는것이다. 아래 예시코드를 보자.
class BorrowableItem {
public :
void checkOut();
};
class ElectronicGadget {
private :
bool checkOut() const;
};
class MP3Player :
public BorrowableItem,
public ElectronicGadget
{ … };
MP3Player mp;
mp.checkOut(); // 모호성 발생 ! 어느 checkOut() ?
* 풀이 : 다중상속시 동일한 이름(checkOut()) 을 하나는 public으로, 다른하나는 private으로 상속받는다.
접근영역이 달라 구분가능해보이지만(private은 직접접근불가이므로) 컴파일러의 알고리즘상 에러를 발생시킨다. (이름중복에러)
*해결방법 : 모호성해결방법을 위해 타입을 지정해주어 해결한다.
mp.BorrowableItem::checkOut(); // 아하, 이 check 함수 이구나!
2. 죽음의 마름모꼴과 가상상속
다중상속(Multiple Inheritance) MI 중 "죽음의 MI 마름모꼴" 이라고 알려진 좋지 않은 모양이 있다.
마름모꼴로 다중상속시 inputFile과 OutFile의 데이터멤버가 IOFile에서 중복될 것이다.
*해결방법 : 한 개의 기본클래스(File)를 2개의 클래스(InputFile, OutputFile)가 "가상상속"한다면 File의 객체가 1개만 생성되므로 중복을 방지할 수 있다.
*문제점 : 하지만, 가상상속시 발생하는 초기화, 가상테이블 등과같은 문제로 비용이 비싸다..
*결론 : 쓸필요 없으면 가상상속은 피해라, 써야한다면 가상클래스에는 데이터를 넣지 말아라
3. 적법한 다중상속 사용 예
class IPerson //인터페이스 IPerson 역할
{
public :
virtual ~IPerson() = 0;
virtual std::string name() const = 0;
virtual std::string birthDate() const = 0;
};
class DatabaseID {…} ;
class PersonInfo
{
public :
explicit PersonInfo( DatabaseID id );
virtual ~PersonInfo() = 0;
virtual const char* theName() const {
static const int MaxFormattedFieldLength = 256;
static char value[MaxFormattedFieldLength];
std::strcpy(value, valueDelimOpen()); // Open 구분자 삽입
.. // Name 에 값 대입
std::strcpy(value, valueDelimClose()); // Close 구분자 삽입
return value;
}
virtual const char* theBirthDay() const;
virtual const char* valueDelimOpen() const { return "["; };
virtual const char* valueDelimClose() const { return "]"; };
};
* 풀이
IPerson : 순수가상 class이다. 해상 클래스를 이용해 CPerson 클래스를 만들려 한다.
PersonInfo : 가정해보자. 예전에 사용하던 클래스(PersonInfo)에 내가 필요한 기능(구분자관련)이 있어, is-imple… 할 클래스이다. (private 상속으로 is-imple.. 구현기법을 사용한다.)
자, 앞으로 구현할 CPerson의 요구사항은 구분자가 없는 상태를 원한다.
기본적인 형태는 IPerson을 가져오나, 일부기능(구분자 관련기능)만 PersonInfo에서 가져올 것이다. 가상함수 재정의가 필요하므로 private 상속으로 PersonInfo를 상속 받아오면되겠다.
class CPerson : public IPerson, private PersonInfo // 다중상속이 사용된다.
{
public :
explicit CPerson(DatabaseID pid) : PersonInfo(pid) {}
virtual std::string name() const
{return PersonInfo::theName()}
virtual std::string birthDate() const
{return PersonInfo::theBirthDate();}
private :
const char * valueDelimOpen() const {return "";} // PersonInfo의 기능을 재정의 1
const char * valueDelimClose() const {return "";} // PersonInfo의 기능을 재정의 2
};
다중 상속은 대단한게 아니다. 그냥 객체 지향기법 중 하나이다. 단일 상속에 비해 복잡하고 이해하기도 복잡하다.
만약 MI설계와 동등한 효과의 SI가 있다면 SI 사용해라.
물론, 명료하고 유지보수성도 가장 좋으며 그 방법으로 MI가 될 수도 있다. '이때다' 싶으면 MI를 질러보자
이것만은 잊지 말자!
- 다중상속은 단일 상속보다 복잡하다, 모호성이 있으며, 가상상속(비용이많이드는)이 필요할수도 있다.
- 가상상속을 사용하면, 크기 비용, 속도비용이 늘어난다. 초기화 및 대입연산의 복잡도고 커진다. 따라서 가상기본클래스에는 데이터를 두지않는것이 좋다.
- 다중상속을 적법하게 사용하는 경우가 있다. 그중 하나는 인터페이스 클래스로부터 Public 상속시킴과 동시에, 구현을 돕는 클래스로부터 private 상속을 시키는 것이다.
'책 정리 > Effective C++ 3rd' 카테고리의 다른 글
항목 42. typename의 두 가지 의미를 제대로 파악하자 (0) | 2020.03.17 |
---|---|
항목 41. 템플릿 프로그래밍의 천릿길도 암시적 인터페이스와 컴파일 타임 다형성부터 (0) | 2020.03.17 |
항목 39. private 상속은 심사숙고해서 구사하자 (0) | 2020.03.16 |
항목 38. "has-a(…는 …를 가짐)" 혹은 "is-implemented-in-terms-of(…는…를 써서 구현됨)"를 모형화할 때는 객체 합성을 사용하자 (0) | 2020.03.12 |
항목 37. 어떤 함수에 대해서도 상속받은 기본 매개변수 값은 절대로 정의하지 말자 (0) | 2020.03.10 |
댓글