일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | 3 | 4 | |||
5 | 6 | 7 | 8 | 9 | 10 | 11 |
12 | 13 | 14 | 15 | 16 | 17 | 18 |
19 | 20 | 21 | 22 | 23 | 24 | 25 |
26 | 27 | 28 | 29 | 30 | 31 |
Tags
- 메타테이블
- 예외
- 루아
- virtual function
- 비교 함수 객체
- 게임
- lua
- 참조자
- Smart Pointer
- 반복자
- 티스토리챌린지
- exception
- reference
- 암시적 변환
- effective stl
- implicit conversion
- 영화 리뷰
- 함수 객체
- UE4
- 상속
- more effective c++
- resource management class
- 언리얼
- 다형성
- operator new
- 스마트 포인터
- Effective c++
- 오블완
- c++
- 영화
Archives
- Today
- Total
스토리텔링 개발자
[Effective C++] 39. private 상속 본문
728x90
항목 39. private 상속은 심사숙고해서 구사하자
private 상속의 동작
- public 상속과 달리, 컴파일러는 일반적으로 파생클래스 객체를 기본클래스 객체로 변환하지 않는다.
- 즉, 기본 클래스를 매개변수로 받는 함수를 파생 클래스 객체로 호출할 수 없다는 의미이다.
- 기본 클래스에서 물려받은 멤버는 파생 클래스에서 모조리 private 멤버가 된다.
- 즉, 기본 클래스의 public,. protected 멤버는 파생 클래스에서 private 멤버가 된다.
private 상속의 의미
- is implemented in terms of (...는 ...를 써서 구현된다.)
- 즉 private 상속은 그 자체로 구현 기법 중의 하나라고 할 수 있다.
- 구현만 물려받을 수 있고 인터페이스는 물려받을 수 없다.
- 소프트웨어 설계(design) 도중에는 아무런 의미도 갖지 않으며, 단지 소프트웨어 구현(implementation) 중에만 의미를 가진다.
private 상속과 객체 합성 중 어떤 걸 선택할 것인가?
- private 상속과 객체 합성은 같은 뜻(is implemented in terms of)을 가진다.
- 할 수 있으면 객체 합성으로 하되 꼭 해야 한다면 private 상속을 사용하자.
- 아무래도 객체 합성 쪽이 가독성이 좋을 것이다.
private 상속을 꼭 해야 하는 경우
- 비공개(protected) 멤버에 접근해야 하는 경우
- 객체 합성으로는 합성된 객체의 protected 멤버에 접근할 수 없다.
- 가상함수를 재정의해야 하는 경우
- 객체 합성으로는 합성된 객체의 virtual 함수를 재정의하기 까다롭다.
- 공간 최적화가 얽힌 '만약의 경우'
- 공백 기본 클래스 최적화. 아래에서 자세히 알아보기로 한다.
is-implemented-in-terms-of 구현의 두 가지 방법
class Timer
{
public:
explicit Timer(int tickFrequency);
virtual void onTick() const;
...
};
// private 상속을 활용한 방식
class Widget : private Timer
{
private:
virtual void onTick() const;
...
};
// 객체 합성을 활용한 방식
class Widget
{
private:
class WidgetTimer : public Timer
{
public:
virtual void onTick() const;
...
};
WidgetTimer timer;
...
};
private 상속 대신 객체 합성으로 구현할 때의 장단점
- 단점
- 객체 합성으로 구현할 시, private 상속에 비해 구조 복잡도가 올라간다.
- 장점
- Widget 클래스를 설계할 때, 파생 클래스에서 onTick을 재정의할 수 없도록 설계 차원에서 막고 싶을 때 유용하다.
- 자바의 final, C#의 sealed를 대체할 아이디어라 할 수 있지만, 모던 C++에서는 언어 상으로 final을 지원한다.
- 컴파일 의존성을 최소화하고 싶을 때 좋다.
- WidgetTimer 객체에 대한 포인터를 가지도록 하고, WidgetTimer의 정의를 다른 헤더로 빼는 것만으로도 컴파일 의존성을 줄일 수 있을 것이다.
- Widget 클래스를 설계할 때, 파생 클래스에서 onTick을 재정의할 수 없도록 설계 차원에서 막고 싶을 때 유용하다.
private 상속을 사용해야 하는, 공간 최적화가 얽힌 만약의 경우
- 공백 클래스(empty class) (데이터가 전혀 없는 클래스)
- 비정적 데이터 멤버가 하나도 없어야 한다.
- static 데이터 멤버는 허용된다.
- 가상 함수가 하나도 없어야 한다.
- 가상 함수가 한 개라도 있으면 각 객체마다 vptr이 하나씩 추가되기 때문이다.
- 가상 기본 클래스도 없어야 한다.
- 가상 기본 클래스는 크기 오버헤드를 일으키는 요인이다.
- 비정적 데이터 멤버가 하나도 없어야 한다.
- 이런 공백 클래스는 개념적으로 차지하는 메모리 공간이 없는 것이 맞다.
- 하지만, C++에는 "독립 구조(freestanding)의 객체는 반드시 크기가 0을 넘어야 한다"는 금기사항이 있다.
// 공백 클래스
class Empty {};
// 공백 클래스 + int 이므로 크기는 sizeof(int)여야 할 것 같지만..
class HoldsAnInt
{
private:
int x;
Empty e;
};
- 이 경우, sizeof(HoldsAnInt) > sizeof(int) 가 되는 괴현상.
- 크기가 달라지는 이유
- 크기가 0인 독립구조 객체가 생기는 것을 금지한다는 제약을 지키기 위함.
- 대부분의 컴파일러는 char 한개를 끼우는 식으로 처리한다.
- 바이트 정렬(alignment)(항목 50 참조)이 필요해지는 경우가 생긴다.
- 바이트 패딩(padding) 과정이 추가되면서, char 하나의 크기를 넘기게 된다.
- 보통은 int 크기 정도로 늘어난다.
- 크기가 0인 독립구조 객체가 생기는 것을 금지한다는 제약을 지키기 위함.
- 하지만 이 C++ 제약(크기가 0인 독립구조 객체가 생기는 것을 금지한다.)은 파생 클래스 객체의 기본 클래스 부분에는 적용되지 않는다.
- 기본 클래스 부분은 독립구조 객체가 아니기 때문이다.
- 그렇다면 이렇게 구현하면?
class HoldsAnInt : private Empty
{
private:
int x;
};
- 이 경우, sizeof(HoldsAnInt) == sizeof(int) 를 만족하게 된다.
공백 기본 클래스 최적화(empty base optimization : EBO)
- private 상속을 활용하여 공백 클래스의 크기를 최적화한다.
- 일반적으로 단일 상속 하에서만 적용한다.
- 사실 공백 클래스는 진짜로 텅 빈 것이 아니다.
- 비정적 데이터는 없지만...
- typedef, enum, 정적 데이터 멤버, 비가상 함수 등을 가질 수 있다.
- STL에서는 기술적으로 공백 처리된 클래스가 많이 있다.
- unary_function
- binary_function
- 사용자 정의 함수 객체를 만들 때 상속시킬 기본 클래스로 굉장히 자주 사용되는 클래스들이다.
- 공백 클래스들이므로, 공백 기본 클래스 최적화가 적용된다.
728x90
'개발 > Effective C++' 카테고리의 다른 글
[Effective C++] 41. 템플릿 프로그래밍 (0) | 2024.07.11 |
---|---|
[Effective C++] 40. 다중 상속 (0) | 2024.07.10 |
[Effective C++] 38. 객체 합성 (0) | 2024.07.08 |
[Effective C++] 37. 상속된 함수의 매개변수 기본값 재정의 문제 (0) | 2024.07.05 |
[Effective C++] 36. 상속된 비가상 함수 이름 가리기 문제 (0) | 2024.07.05 |
Comments