항목 39 : private 상속은 심사숙고해서 구사하자
private 상속의 의미는 is-implemented-in-terms-of입니다. private 상속은 소프트웨어 설계 도중에는 아무런 의미도 갖지 않으며, 단지 소프트웨어 구현 중에만 의미를 가질 뿐입니다.
private를 언제 사용해야 할지에 대한 궁금증을 가질 수 있습니다.
그에 대한 답변은 간단합니다. 할 수 있으면 객체 합성을 사용하고, 꼭 해야 하면 private 상속을 사용하십시오.
꼭 해야 하는 경우는 주로 "비공개 멤버를 접근할 때 혹은 가상 함수를 재정의할 경우" 입니다.
private 상속 대신에 public 상속에 객체 합성 조합이 더 자주 즐겨 쓰입니다.
두 가지의 좋은 점이 있기 때문입니다.
첫째, 클래스를 설계하는데 있어서 파생은 가능하게 하되, 파생 클래스에서 함수를 재정의할 수 없도록 설계 차원에서 막고 싶을 때 유용합니다.
둘째, 컴파일 의존성을 최소화하고 싶을 때 좋습니다.
하지만 객체 합성보다 private 상속을 선호할 수 밖에 없는 공간 최적화가 얽힌 경우가 있습니다.
데이터가 전혀 없는 클래스를 사용할 때 입니다(비정적 데이터 멤버가 없는 클래스). 그러니까 가상 함수도 하나도 없어야 하고, 가상 기본 클래스도 없어야 합니다. 이런 공백 클래스는 개념적으로 차지하는 메모리 공간이 없는 게 맞습니다. 하지만 이런저런 기술적인 우여곡절 때문에 C++에는 "독립 구조의 객체는 반드시 크기가 0을 넘어야 한다"라는 금기사항 같은 것이 정해져 내려오고 있어서, 다음과 같은 코드를 써 보면
1
2
3
4
5
6
7
|
class Empty { }; // 정의된 데이터가 없으므로, 객체는 메모리를 사용하지 말아야 합니다.
class HoldsAnInt { // int를 저장할 공간만 필요해야 합니다.
private :
int x;
Empty e; // 메모리 요구가 없어야 합니다.
};
Colored by Color Scripter
|
sizeof(HoldsAnInt) > sizeof(int)가 되는 괴현상을 목도하게 됩니다. Empty타입의 데이터 멤버가 메모리를 요구하는 거죠.
눈치가 빠른 사람이라면 아까 객체 크기가 0이면 안 된다는 말을 하면서 '독립구조'라는 말에 신경이 쓰였을 것입니다.
이 C++의 제약은 파생 클래스 객체의 기본 클래스 부분에는 적용되지 않습니다. 이때의 기본 클래스 부분은 독립구조 객체, 다시 말해 홀로서기를 한 객체가 아니기 때문입니다. Empty 타입의 객체를 데이터 멤버로 두지 말고 Empty로부터 상속을 시켜 보면,
1
2
3
4
|
class HoldsAnInt: private Empty {
private :
int x;
};
Colored by Color Scripter
|
sizeof(HoldsAnInt) == sizeof(int)를 확인 할 수 있습니다.
이 공간 절약 기법은 공백 기본 클래스 최적화(empty base optimization: EBO)라고 알려져 있습니다. 메모리 공간에 신경을 쓰는 사용자를 상대하는 라이브러리 개발자라면 EBO를 알아두는 게 좋을 것입니다.
private 상속이 적법한 설계 전략일 가능성이 가장 높은 경우가 있습니다. 아무리 봐 주어도 is-a 관계로 이어질 것 같지 않은 두 클래스를 사용해야 하는데, 이 둘 사이에서 한쪽 클래스가 다른 쪽 클래스의 protected 멤버에 접근해야 하거나 다른 쪽 클래스의 가상 함수를 재정의해야 할 때가 바로 이 경우입니다. 그렇다고 private 상속 아니면 안 되는 것도 아닙니다. public 상속과 객체 합성을 적절히 잘 섞으면, 설계 복잡도는 좀더 올라가겠지만 원하는 동작을 얻을 수 있기 때문입니다. "private 상속을 심사숙고해서 구사하자"라는 말의 의미는, 섣불리 이것을 쓸 필요가 없다는 생각을 갖고 모든 대안을 고민한 연후에, 주어진 상황에서 두 클래스 사이의 관계를 나타낼 가장 좋은 방법이 private 상속이라는 결론이 나면 쓰라는 뜻입니다.
꼭 잊지 말아야 할 것!
1. private 상속의 의미는 is-implemented-in-terms-of(...는 ...를 써서 구현됨)입니다. 대개 객체 합성과 비교해서 쓰이는 분야가 많지는 않지만, 파생 클래스 쪽에서 기본 클래스의 protected 멤버에 접근해야 할 경우 혹은 상속받은 가상 함수를 재정의해야 할 경우에는 private 상속이 나름대로 의미가 있습니다.
2. 객체 합성과 달리, private 상속은 공백 기본 클래스 최적화(EBO)를 활성화시킬 수 있습니다. 이 점은 객체 크기를 가지고 고민하는 라이브러리 개발자에게 꽤 매력적인 특징이 되기도 합니다.
'언어 > C++' 카테고리의 다른 글
[Effective C++] 템플릿 프로그래밍의 천릿길도 암시적 인터페이스와 컴파일 타임 다형성부터 (0) | 2020.02.10 |
---|---|
[Effective C++] 다중 상속은 심사숙고해서 사용하자 (0) | 2020.02.09 |
[Effective C++] "has-a(...는...를 가짐)" 혹은 "is-implemented-in-terms-of(...는...를 써서 구현됨)"를 모형화할 때는 객체 합성을 사용하자 (0) | 2020.02.07 |
[Effective C++] 어떤 함수에 대해서도 상속받은 기본 매개변수 값은 절대로 재정의하지 말자 (0) | 2020.02.06 |
[Effective C++] 상속받은 비가상 함수를 파생 클래스에서 재정의하는 것은 절대 금물! (0) | 2020.02.05 |