본문 바로가기
책 정리/Effective C++ 3rd

항목 40. 다중 상속은 심사숙고해서 사용하자

by ocean20 2020. 3. 16.

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 상속을 시키는 것이다.

댓글