2008.10.26 16:22 책 정리/Exceptional C++

경험 많은 개발자라도 상속을 남용 하는 경우가 많이 있다. 상속은 머리에 껌이 달라 붙는것 처럼 띄어내기가 참 어려운 구조이기 때문에, 필요할때만 사용 해야 한다. 자.. 예제코드를 봐보자.

코드


 질문

1. MySet1과 MySet2 사이에 차이가 있는가?

2. 더 일반적으로 nonpublic 상속과 포함과의 차이가 있나? 왜 포함 대신에 상속을 사용하는지 설명해 보라.

3. 어느 버전의 MySet이 더 좋은가?

4. 왜 public 상속을 사용하는지에 대한 이유를 가능한 많이 설명해 보라.



분석

1.

기능적으로 차이점은 아무것도 없다.


2.

차이를 설명하기 앞서, 몇가지 개념을 설명 한다면


nonpublic 상속

항상 "...에 의한 구현(IS-IMPLEMENTED-IN-TERMS-OF)"를 나타내야 하며, 사용하는 클래스는 사용되는 클래스의 public과 protected 부분에 의존하며 구현 된다.


포함 상속(HAS-A)

항상 "갖고 있음"을 나타내므로, "...에 의한 구현"을 표현하며, 사용 클래스는 사용되어지는 클래스의 public 부분에만 의존하여 구현 되어 진다.

차이를 말하자면, 포함 상속이 nonpublic 상속보다 더 결합도가 떨어진다. 그러니까 머리에 껌이 좀 덜 달라 붙었다는 이야기이다.


그렇다면 언제 포함 대신에 nonpublic 상속을 쓰는가?

바로 포함 상속으로 표현할 수 없는 객체를 표현하고자 할때, 상속을 사용 한다. 정리를 하자면

  1. 가상 함수를 재정의할 경우에 필요하다.
  2. protected 멤버를 접근할 경우에 필요하다.
  3. 다른 기반 클래스의 서브 개체가 만들어지기 전에 사용되는 개체를 생성하거나 서브 개체 이후에 파괴할 경우에 필요 하다.[각주:1]
  4. 일반 가상 기반 클래스를 공유하거나 가상 기반 클래스의 생성자를 재정의할 경우에 필요하다.[각주:2]
  5. 비어 있는 기반 클래스 최적화에도 충분히 이득을 얻을 수 있다.[각주:3]
  6. "제어된 다형성"(어떤 코드에서는 LSP IS-A)에 필요하다.[각주:4]
이것들이 포함 대신 private 상속이나 protected 상속을 하는 이유이다.


3.

어떤게 더 좋은지, 우선 분석해 본다면

  • MyList는 protected 멤버를 가지고 있지 않으므로 이 때문에 상속할 필요가 없다.
  • MyList는 가상 함수가 없으므로 이 때문에 상속할 필요가 없다.
  • MyList는 다른 잠재적인 기반 클래스들이 없으므로, 서브 개체 생성 이전에 생성되거나 이후에 소멸 될 필요가 없으므로 상속할 필요가 없다.
  • MyList는 비어 있지 않으므로, "비어 있는 기반 클래스 클래스 최적화" 목적으로 상속할 필요가 없다.
  • MySet 안에 MySet 함수나 friend가 없다고 하더라도 MySet 은 MyList가 아니다. 즉 IS-NOT-A 관계이다. 그러므로 상속할 필요가 없다.
.. 자 모든 경우에서 상속할 필요가 없다. 그런데 왜 이렇게 상속 구조로 가야 하는가? 서로가 의존하지 않는 다는 것은 보다 견고한 프로그래밍의 기초임에도 불과하고, 왜 자꾸 상속을 하는가? 그것은 가끔 상속이 어울 릴 수가 있기 때문이다.

예를 들자면

 Func1과 같은 가상함수를 재정의 하거나 Func2와 같이 protected 멤버를 접근 해야 할 때, 상속은 필요하다. 그래서 나는 Func1를 재정의 하기 위해 private 상속을 했다고 치자. 다음 코드를 보자.

 여기서 정말 상속 해야만 했을까? 왜 이런 생각을 하게 되었냐면, 나는 단지 Func1 만 재정의 하려고 했는데 불필요하게 Func2()의 접근 권한까지 얻게 되어진 점이 매우 만족스럽지 못하게 하기 때문이다.

Derived의 모든 멤버는 불필요한 Func2()의 접근 권한을 갖게 되었다는 점이 문제인 것이다. 이것을 해결 하려면 어떻게 상속을 해야 할까?

해결 코드

어떤가 실제적으로 Derived는 재정의된 Fuc1만 접근할 수 있는 권한만 얻게 된 것이다. Base의 Func1 을 DerivedImpl이 nonpublic 상속하여 Func1을 재정의하고 별도로 필요한 Func2() 접근까지 만들어 두고 실제로 쓰는 Derived 클래스는 포함 상속 함으로써, Impl의 공개 인터페이스만 접근 가능하게 된 것이다.

이제 머리에 붙은 껌을 제거하게 된 것이다.

이것은 포함 상속의 이점을 살린것인데, 몇 가지 변형된 포함 상속을 살펴보자.

첫번째로는 파생 보조클래스의 사본을 여러개 포함함으로써 다중 인스턴스 제공이 가능해 진다.
두번째로는 런타임에 파생 보조클래스의 인스턴스를 교체할 수 있다.[각주:5]


부수적으로 MySet1 이나 MySet2 보다 더 일반적인 포함 방식을 사용 할 수 있다.

코드

 이 기법으로 T에 대한 MyList및 다른 MyList 를 사용 할 수 있게 된다. 이 기법은 STL의 컨테이너에 사용 되어 졌다. 바로 "할당자" 부분에...

바로 이런 부분 때문에 불필요한 nonpublic 상속이나 public 상속은 완전 불가능하게 만드는 것이 생기고 만다는 데에 문제가 있다.


4.

public 상속은 개념의 상속으로 봐야 한다. 대체로 개념은 A 같이 행동하는 B를 만들기 위해서, 즉 B가 A일 필요가 있을 경우 public 상속을 해야 한다.

이 규칙을 따르게 되면, 일반적인 두개의 함정을 피하게 된다.

  • public 상속이 필요한데 nonpublic 상속을 해버리는 함정
  • IS-ALMOST-A를 구현하는데 public을 사용 하는 함정
두번째 함정은 조금 설명이 필요하다. A 클래스와 대체로 같다는것은 사용하는 B와는 전혀 다른 클래스인데도 불과하고, public 상속을 하는 함정이다.

정사각형은 직사각형이므로 class 정사격형 : public 직사각형 으로 되겠지만, 우리의 클래스 입장에선 꼭 이럴 필요는 없다는 것이다.

public 상속은 꼭 필요한 경우만 사용 해야 한다. 물론 대부분의 사람들이 "어차피 비슷한거니까 IS-ALOMOST-A 를 public으로 해도 약간 손해 봐요~" 라고 말하지만 허브셔터는 "그 약간의 손해는 당신을 해고 할 만큼 큰 비용을 내야 할 꺼에요.[각주:6]" 라고 말한다. ㅋ


총평

결합도가 낮을 수록 보다 유연하며, 보다 견고해 진다는 것을 알았다. .. 지금까지 짯던 모든 코드들이 애들 장난이였구나 싶다. Effective C++ 은 C++ 의 국물 맛 보기 라면 Exceptional C++ 은 국물 떠 먹기다. ㅋ




  1. 상속된 개체가 먼저 생성 되고, 제일 나중에 파괴 된다. [본문으로]
  2. 이 부분은 사용한 적이 없어서 이해 못했다. 문맥상 이해한 것으로는 가상 기반 클래스의 공유나 가상 기반 클래스의 생성자를 재정의는 상속밖에 답이 없기 때문이다. [본문으로]
  3. 비어 있는 클래스는 최소한 1Byte를 차지한다. 하지만 상속하면 이 경우는 없어 진다. [본문으로]
  4. 즉, 다형성을 제어 할 수 있게 된다. 사용하는 클래스 외부에선 다형성을 사용 할 순 없지만, 사용하는 클래스 내부에선 다형성이 적용 받는다. 반쪽짜리 다형성 이라 보면 된다. [본문으로]
  5. 이럴 경우 포인터로 작업하는게 더 편할 것이다. 핌플~ Pimpl [본문으로]
  6. 문맥상 이렇다는 거다. 막대한 시간과 노력을 허비된다는 표현이 원래 [본문으로]
신고
posted by 농사를 짓는 게임 프로그래머 최익필

댓글을 달아 주세요

  1. Favicon of http://ikpil.com 게임 프로그래머라 불릴 최익필 2008.10.26 16:53 신고  Addr  Edit/Del  Reply

    오캄의 면도날이 다시 생각나게 한다. 굳이 복잡하게 할 필요가 없다면, 아예 쓰지 않는게 제일 좋을 듯 싶다.

  2. Favicon of http://ikpil.com 게임 프로그래머라 불릴 최익필 2008.10.26 17:07 신고  Addr  Edit/Del  Reply

    프로그래머는 단지 코딩하는 사람이 아니라, 마치 경제학자 처럼 모든 상황에 적합한지 분석도 할 수 있는 능력이 필요 한거 같다. - 기록 기록~