스토리텔링 개발자

[Effective C++] 40. 다중 상속 본문

개발/Effective C++

[Effective C++] 40. 다중 상속

김디트 2024. 7. 10. 11:13
728x90

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

 

 

 

다중 상속(multiple inheritance : MI)의 문제
  1. 둘 이상의 기본 클래스로부터 똑같은 이름을 물려받을 가능성이 생긴다.
    • class BorrowableItem
      {
      public:
          void checkOut();
          ...
      };
      
      class ElectronicGadget
      {
      private:
          bool checkOut() const;
          ...
      };
      
      // 이런 다중 상속의 경우 어떻게 될 것인가?
      class MP3Player : public BorrowableItem,
                        public ElectronicGadget
      { ... };
      
      MP3Player mp;
      mp.checkOut(); // 모호성 발생!
    • 위에서 확인할 수 있듯, 파생 클래스가 접근할 수 있는 함수가 딱 결정되는 게 분명해도 모호성이 발생한다.
      • BorrowableItem에서는 public 멤버이지만, ElectronicGadget에서는 private 멤버이다.
      • 하지만 이 경우에도 모호성이 발생한다.
    • C++ 컴파일러가 중복된 함수 호출 중 하나를 골라내는 규칙
      1. 최적 일치 함수를 찾는다.
      2. 함수의 접근 가능성을 점검한다.
    • 위의 경우 일치도가 서로 같기 때문에 최적 일치 함수가 결정되지 않고, 그렇기에 함수 접근 가능성 점검조차 하지 않고 모호성이 발생하는 것이다.
    • 해결법
      • 명확히 사용하고자 하는 함수를 명시하자. 
      • mp.BorrowableItem::checkOut();
  2. 죽음의 MI 마름모꼴(deadly MI diamond)
    • class File { ... };
      
      class InputFile : public File { ... };
      class OutputFile : public File { ... };
      
      // 죽음의 MI 마름모꼴 발생!
      // File의 데이터 멤버가 중복 생성된다!!
      class IOFile : public InputFile,
                     public OutputFile
      { ... };
    • 기본 클래스와 파생 클래스 사이 경로가 두개 이상 되는 상속 계통.
    • 기본 클래스 데이터 멤버가 중복 생성된다 vs 기본 클래스 데이터 멤버가 중복되지 않도록 한다.
      • 무엇이 맞는 것일까? 둘 다 맞는 거 같기도 한데..
    • C++은 두 가지 방법 모두를 지원한다.
      • 기본적으로는 기본 클래스의 데이터 멤버를 중복 생성한다.
      • 기본 클래스를 가상 기본 클래스(virtual base class)로 만드는 것으로 중복 생성하지 않도록 할 수 있다.(가상 상속)

 

 

 

가상 상속(virtual inheritance)
  • 가상 기본 클래스로 삼을 클래스에 직접 연결된 파생 클래스에서 가상 상속(virtual inheritance)을 사용하게 만든다.
  • class File { ... };
    
    // 가상 상속
    class InputFile : virtual public File { ... };
    class OutputFile : virtual public File { ... };
    
    class IOFile : public InputFile,
                   public OutputFile
    { ... }
  • 표준 C++ 라이브러리에서 가상 상속을 활용하는 예시
    • basic_ios, basic_istream, basic_ostream, basic_iostream
  • 가상 상속과 일반 상속을 섞어 쓰는 경우 어떻게 될까?
    • 가상 상속된 것들끼리의 멤버 데이터는 중복되지 않지만, 가상 상속되지 않은 것의 멤버 데이터는 중복 생성된다.
  • 즉, 정확한 동작의 관점에서는 public 상속은 항상 반드시 가상 상속으로 하는 것이 맞을 테지만...

 

 

 

가상 상속의 문제점
  1. 비용이 발생한다.
    • 컴파일러는 중복 생성을 막기 위해 꼼수를 사용한다.
      • 때문에 가상 상속 한 경우의 클래스 크기가 일반적으로 더 크다.
      • 데이터 멤버에 접근하는 속도도 느리다.
  2. 초기화 규칙이 훨씬 복잡하고 직관성도 떨어진다.
    •  가상 상속의 초기화 규칙
      1. 초기화가 필요한 가상 기본 클래스로부터 클래스가 파생된 경우
        • 이 파생 클래스는 가상 기본 클래스와의 거리에 상관없이 가상 기본 클래스의 초기화를 염두에 두어야 한다.
      2. 기존의 클래스 계통에 파생 클래스를 새로 추가하는 경우
        • 그 파생 클래스는 가상 기본 클래스의 초기화를, 거리에 상관 없이, 떠맡아야 한다.

 

 

 

결론
  • 구태여 쓸 필요 없으면 가상 상속을 사용하지 말자.
  • 굳이 사용해야 한다면, 가상 기본 클래스에는 멤버 데이터를 넣지 말자.
    • 가상 기본 클래스의 초기화 규칙으로부터 해방된다.
    • 즉, 자바의 interface 개념으로만 사용하자.
728x90
Comments