이펙티브 C++
https://www.yes24.com/Product/Goods/17525589
페이지 279p ~ 290p
이펙티브 C++(279p ~ 290p)
요약
항목 39: private 상속은 심사숙고해서 구사하자
- C++는 public 상속을 is-a 관계로 나타낸다.
- 앞서 public 상속 관계로 구현된 Person, Student를 private 상속으로 바꿔보자.
class Person {};
class Student : private Person {};
void eat(const Person& p);
void study(const Student& s);
Person p;
Student s;
int func()
{
eat(p);
// 에러 발생. Student는 Person의 일종이 아님
eat(s);
}
- 클래스 사이의 상속 관계가 private이면 컴파일러는 일반적으로 파생 클래스 객체를 기본 클래스 객체로 변환하지 않는다.
- eat 함수에 s를 매개변수로 넣었을 때 에러가 발생하는 원인이기도 하다.
- 또한, private으로 상속받은 멤버는 파생 클래스에서 모조리 private 멤버가 된다.
- 기본 클래스에서 public이나 protected 멤버였던 것들도 모두 private이 된다. 예외가 없다.
- 결국 private 상속은 is-implemented-in-terms-of의 의미를 가진다.
- B 클래스로부터 private 상속을 통해 D 클래스를 파생시킨 것은, B 클래스에서 쓸 수 있는 기능들 몇 개를 활용할 목적으로 한 행동이다.
- 이는 B 타입과 D 타입 사이에 어떤 개념적 관계가 있어서 그렇게 한 것이 아니다.
- private 상속은 구현만 물려받을 수 있다는 의미와 같다.
- D는 B를 private 상속받으면 D 객체가 B를 써서 구현되는 거라고 생각하면 된다.
- private 상속은 설게 도중에는 아무런 의미가 없으며, 단지 소프트웨어 구현 중에만 의미를 가진다.
- 객체 합성과 private 상속이 모두 같은 의미(is-implemented-in-terms-of)이지만, 할 수 있다면 객체 합성을 사용하고, 꼭 해야 하면 private 상속을 사용하자.
- private 상속을 꼭 사용해야 하는 경우는 다음과 같다.
- 비공개 멤버 접근
- 가상 함수 재정의
class Timer {
public:
explicit Timer(int tickFrequency);
virtual void onTick();
};
class Widget : private Timer {
private:
virtual void onTick() const;
};
- Widget 클래스는 어떤 응용 프로그램 개발에 사용되는 클래스인데, 이 클래스의 함수 호출 횟수와 같은 추적 기능을 달아주고 싶다.
- Timer 클래스는 반복적으로 시간을 경과시킬 주기를 정할 수 있고, 일정 시간이 경과할 때마다 가상 함수를 호출하도록 되어 있다.
- 따라서 이 가상 함수(onTick)을 재정의하면 Widget 클래스에서도 일정 주기마다 실행시킬 함수를 손쉽게 구현할 수 있다.
- Widget은 Timer의 한 계통이 아니기 때문에 public 상속이 아니라 private 상속을 받는다.
- Widget 객체의 사용자는 Widget 객체를 통해 onTick 함수를 호출해선 안 된다.
- onTick 함수는 사용자를 위해 제공되는 인터페이스의 개념이 아니기 때문이다.
- private 상속을 통해 Timer의 public 멤버인 onTick은 Widget에서는 private이 된다.
- 객체 합성으로는 다음과 같이 구현할 수 있을 것이다.
class Widget {
private:
class WidgetTimer : public Timer
{
public:
virtual void onTick() const;
};
WidgetTimer timer;
};
- 현실적으로는 이런 상황에서는 private 상속보다는 객체 합성을 더 자주 사용한다.
- Widget 클래스를 설계할 때 파생은 가능하게 하되, 파생 클래스에서 onTick을 재정의할 수 없도록 설계 차원에서 막고 싶을 때 객체 합성이 더 유용하다.
- private 상속을 사용하더라도 파생 클래스는 자신이 물려받은 가상 함수를 호출할 순 없어도 재정의할 수는 있기 때문이다.
- 객체 합성을 사용하는 경우, Timer를 상속받은 WidgetTimer가 Widget 클래스의 private 영역에 있으면, Widget 클래스의 파생 클래스는 아무리 용을 써도 WidgetTimer에 접근할 수 없다.
- Widget의 컴파일 의존성을 최소화하고 싶을 때 좋다. Widget이 Timer에서 파생된 상태라면, Widget이 컴파일될 때, Timer의 정의도 끌어올 수 있어야 하기 때문에 Timer.h 같은 헤더를 인클루드 해야 할지도 모른다.
- 객체 합성을 사용하면 WidgetTimer를 Widget의 외부로 빼내고, WidgetTimer 객체에 대한 포인터만 갖도록 만들어 두면, WidgetTimer 클래스를 간단히 선언하는 것만으로도 컴파일 의존성을 피할 수 있다.
- 규모가 큰 시스템을 만들 때 이런 구성요소 분리는 중요해질 수 있는 요소이다.
- Widget 클래스를 설계할 때 파생은 가능하게 하되, 파생 클래스에서 onTick을 재정의할 수 없도록 설계 차원에서 막고 싶을 때 객체 합성이 더 유용하다.
항목 40: 다중 상속은 심사숙고해서 사용하자
- C++은 다중 상속을 지원한다.
- 다중 상속을 지원하면 둘 이상의 기본 클래스로부터 똑같은 이름을 물려받을 가능성이 생겨 버린다.
- 다중 상속은 이러한 이유 때문에 모호성이 발생할 가능성이 높다.
class BorrowableItem {
public:
void checkOut();
};
class ElectronicGadget {
private:
bool checkOut() const;
};
class MP3Player : public BorrowableItem, public ElectronicGadget {
};
MP3Player mp;
mp.checkOut;
- checkOut 함수들 중에서 파생 클래스가 접근할 수 있는 함수가 딱 결정되어 있는데도 모호성이 발생한다.
- 이는 중복된 함수 호출 중 하나를 골라내는 C++의 규칙을 따른 결과이다.
- C++ 컴파일러는 이 규칙을 기반으로 주어진 호출에 대해 최적으로 일치하는 함수인지를 먼저 확인한다.
- 그리고 나서 함수의 접근가능성을 검토한다.
- checkOut 함수는 C++ 규칙에 의한 일치도가 서로 같기 때문에 최적 일치 함수 결정이 되지 않고 있다.
- public checkOut 함수의 접근 가능성은 검토되지도 않는다.
- 이러한 모호성을 해소하려면, 호출할 기본 클래스의 함수를 손수 지정해주어야 한다.
mp.BorrowableItem::checkOut();
- 다중 상속의 의미는 그냥 단순히 둘 이상의 클래스로부터 상속을 받는 것이지만, 상위 단계의 기본 클래스를 여러 개 갖는 클래스 계통에서 심심치 않게 눈에 띈다.
메모
항목 39: private 상속은 심사숙고해서 구사하자
- private 상속의 의미는 is-implemented-in-terms-of이다. 대개 객체 합성과 비교해서 쓰이는 분야가 많지는 않지만, 파생 클래스 쪽에서 기본 클래스의 protected 멤버에 접근해야 할 경우 혹은 상속받은 가상 함수를 재정의해야 할 경우에는 private 상속이 나름대로 의미가 있다.
- 객체 합성과 달리 private 상속은 공백 기본 클래스 최적화를 활성화시킬 수 있다. 이 점은 객체 크기를 가지고 고민하는 라이브러리 개발자에게 꽤 매력적인 특징이 되기도 한다.
항목 40: 다중 상속은 심사숙고해서 사용하자
- 다중 상속은 단일 상속보다 확실히 복잡하다. 새로운 모호성 문제를 일으킬 뿐만 아니라 가상 상속이 필요해질 수도 있다.
- 가상 상속을 쓰면 크기 비용, 속도 비용이 늘어나며, 초기화 및 대입 연산의 복잡도가 커진다. 따라서 가상 기본 클래스에는 데이터를 두지 않는 것이 현실적으로 가장 실용적이다.
- 다중 상속을 적법하게 쓸 수 있는 경우가 있다. 여러 시나리오 중 하나는, 인터페이스 클래스로부터 public 상속을 시킴과 동시에 구현을 돕는 클래스로부터 private 상속을 시키는 것이다.
'책 > 이펙티브 C++' 카테고리의 다른 글
이펙티브 C++(306p ~ 312p) (1) | 2024.02.06 |
---|---|
이펙티브 C++(295p ~ 306p) (0) | 2024.02.05 |
이펙티브 C++(270p ~ 279p) (0) | 2024.02.02 |
이펙티브 C++(261p ~ 270p) (0) | 2024.02.01 |
이펙티브 C++(250p ~ 261p) (0) | 2024.01.31 |