ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • [Effective C++] 6장(1) - 상속, 그리고 객체 지향 설계
    쾌락없는 책임 (공부)/Effetive C++ 요약본 2022. 11. 29. 21:42
    반응형
    본 카테고리는 프로텍 미디어의 '이펙티브 C++'을 보고 요약하는 카테고리입니다.

    3판을 기준으로 하며 전체 내용이 아닌 간략한 내용만을 요약하고 있습니다.

     

     

    항목 32 : public 상속 모형은 반드시 "is-a"를 따르도록 만들자

    본 "Effective C++"을 작성한 스콧 마이어스가 가장 강조한 부분으로 만일 public 으로 상속을 해 준다면 무조건 is-a 관계의 타입이 되어야 한다는 이야기입니다.

     

    다만 여기서 나온 이야기들 중 '새-펭귄' 의 예시가 인상깊게 남습니다. 이 경우 '새'는 날 수 있을지언정 '펭귄'은 날 수 없기에 is-a 관계를 주면서 이상한 복잡성을 가지게 됩니다. 책에서는 bird 클래스에 fly를 넣지 말고 canflybird 클래스를 파생시킨 뒤 fly를 넣어주는게 좋다는 이야기를 했습니다.

     

    아무튼 간단하게 요약하면 public 상속은 기본 클래스 객체가 가진 '모든' 것들이 파생 클래스에 그대로 적용된다고 단정하는 방식입니다.

     

     

    항목 33 : 상속된 이름을 숨기는 일은 피하자

    Scope와 관련한 이야기를 하는 항목으로 public 상속인 경우에는 이런 경우를 피해야 한다는걸 이야기 해 줍니다.

    다만 private 상속에서는 이런 경우가 말이 될 수 있으며

    class Derived : private Base{
    public:
    	virtual void mf1()   // 전달 함수입니다. 암시적으로
    	{ Base::mf1(); }     // 인라인 함수가 됩니다.
    	...
    };
    
    ...
    
    Derived d;
    int x;
    
    d.mf1();   // Good!    Call Derived::mf1
    d.mf1(x);  // Error!

    이런 식의 코드를 작성하는 일이 있을 수 있다고 합니다.

     

     

     

    항목 34 : 인터페이스 상속과 구현 상속의 차이를 제대로 파악하고 구별하자

    public 상속은 2가지 형태로 나뉘게 됩니다. 함수 인터페이스 상속/함수 구현 상속. 이와 관련해서 함수가 3가지 나뉘게 되는데 정리해보면 순수 가상 함수, 단순 가상 함수, 비가상 함수 3가지 입니다.

     

    순수 가상 함수 : 함수의 인터페이스만을 물려주는게 목표
    단순 가상 함수 : 인터페이스 뿐 아니라 구현도 물려받게 하는 것
    비가상 함수 : 클래스 파생에 관계 없는 필수적인 구현으로 '불변동작'

     

    그리고 중간 단순 가상 함수와 관련한 이야기들 중 '다른 클래스들은 기본 동작을 하는데 한 클래스가 기본 동작을 하지 않는다면 물려받지 않게 하는 법' 과 관련한 이야기가 나옵니다.

    class Airport {...};
    
    class Airplane{
    public :
    	virtual void fly(const Airport& destination);
    	...
    };
    
    void Airplane::fly(const Airport& destination){
    	// 주어진 목적지로 비행기를 날리는 함수
    };
    
    class ModelA : public Airplane {...};
    class ModelB : public Airplane {...};

    여기에서 기본 fly 동작을 하지 않는 ModelC를 파생하다가 fly를 재정의하지 않으면 문제가 됩니다. 그래서 가상 함수 인터페이스와 그 가상 함수의 기본 구현을 잇는 연결 관계를 끊으면 됩니다.

    class Airplane {
    public:
    	virtual void fly(const Airport& destination) = 0;
    	...
    protected:
    	void defaultFly(const Airport& destination);
    };
    
    void Airplane::defaultFly(const Airport& destination)
    {
    //...
    }
    
    class ModelA : public Airplane{
    public:
    	virtual void fly(const Airport& destination)
    	{ defaultFly(destination); }
    	...
    };

    이런 식으로 defaultFly 같은 비가상 기본 함수를 만들어서 처리하는 방법이 있습니다.

     

    순수 가상 함수, 단순 가상 함수, 비가상 함수 3가지 선택지가 개성이 있다보니 고를 때 신중히 골라야 합니다. 이런 과정들 중에서 자주 하는 2가지 실수를 보도록 하겠습니다.

     

    첫번째 실수 - 모든 멤버 함수를 비가상 함수로 선언
    상속 관계에서 비가상 소멸자가 문제가 되며 상속을 여지에 둔다면 가상 함수를 두는게 일반적입니다.

    두번째 실수 - 모든 함수를 가상 함수로 선언하는 것
    인터페이스의 경우 맞는 이야기지만 한번 잘 살펴보는게 좋습니다.

     

     

    항목 35 : 가상 함수 대신 쓸 것들도 생각해 두는 자세를 시시때때로 길러 두자

    가상 함수가 사실 디폴트의 전략이지만 다른 전략들도 사용할 수 있다는걸 알아두는게 좋습니다.

     

    • 비가상 인터페이스 관용구
      • 공개되지 않은 가상 함수를 비가상 public 멤버 함수로 감싼다
      • 템플릿 메서드 패턴의 한 형태
    • 가상 함수를 함수 포인터 데이터 멤버로 대체
      • 전략(스트래터지) 패턴의 핵심만을 보여줌
    • 가상 함수를 tr1::function 데이터 멤버로 대체
      • 호환되는 시그니처를 가진 함수호출성 개체를 사용할 수 있도록 함
      • 역시 전략 패턴의 일종
    • 한쪽 클래스 계통에 속한 가상 함수를 다른 쪽 계통에 속해 있는 가상 함수로 대체
      • 전략 패턴의 전통적인 구현

     

     

    항목 36 : 상속받은 비가상 함수를 파생 클래스에서 재정의하는 것은 절대 금물!

    뭐 더 이야기할 필요가 없습니다. 비가상 함수를 가리는 정의를 내리는 코드들이 있는데 이 경우 비가상 함수가 static binding으로 묶이게 되므로 전부 부모 클래스의 이름을 호출하거나, 일관성 없는 동작을 하게됩니다. 

    최악의 방법으로 협업에도 방해가 되니 이 경우에는 가상 함수로 변경하는게 좋습니다.

    반응형

    댓글

Designed by Tistory.