
#include<cstdio>
class Person { };
class Student : private Person{ };
void eat(const Person& p) {}
void study(const Student& s) {}
int main() {
Person p;
Student s;
eat(p);
study(s);
// eat(s); // 에러!
}
private 상속은 public 상속과 대조적으로 파생 객체(자식객체)가 기본 클래스 객체(부모객체)로의 변환이 불가능하다.private상속을 받은 객체의 기능을 사용할 수 있다.private상속은 is-a관계가 아닌 is-implemented-in-terms-of관계로 보는것이 맞다.private 상속의 의미는 “구현만 물려받을 뿐 동일한 인터페이스는 아니다”라고 생각하면 된다.프로파일링을 위해 내가 사용하는 Widget클래스의 호출 횟수, 시간에 따른 호출 빈도를 추적하고 싶다고 가정해보자.
마침, Widget에서 필요로하는 시간 관련 기능이 Timer클래스에 미리 구현돼있다면?
class Timer {
public:
explicit Timer(int tickFrequency) {}
virtual void onTick() const;
};
Timer 대한 기능들에 접근이 가능하고 내가 원하는대로 동작하게 하기 위한 오버라이딩도 가능하다.class Widget : private Timer {
private:
virtual void onTick() const override;
};
has-a관계를 표현하는데 더 적합하지 않나?class Widget{
private:
class WidgetTimer : public Timer {
public:
virtual void onTick() const override;
};
WidgetTimer timer;
};
Widget을 상속받는 파생 클래스에서 onTick()함수를 오버라이딩 하는 등의 상황을 막을 수 있다. (C++은 C#처럼 sealed 키워드를 지원하지 않기 때문 ->사실 이부분도 C++11에서 override키워드와 함께 final 키워드를 지원하기 시작함)Timer를 상속받으려면 선언부에서 헤더파일을 include해야 하기에 컴파일 의존성이 생기지만 컴포지션 패턴을 사용하면 전방선언을 활용하고 선언부가 아닌 구현부에서 include해도 되기에 컴파일 의존성을 줄일 수 있다.protected로 선언돼있다면 위에서 했던 것처럼 Timer클래스를 WidgetTimer로 상속받은 이후 필요한 정보들을 노출시킬 수 있는 함수를 두면 된다.private이라면 어차피 Timer클래스를 Widget 클래스에 private상속해도 어차피 private멤버에 대한 접근은 불가능하다.Timer클래스를 WidgetTimer로 상속받고 재정의 하면 된다.class Empty {};
class HoldsAnInt {
int x;
Empty e;
};
int main() {
printf("%d byte\n",sizeof(HoldsAnInt));
return 0;
};
8 byte가 나온다. 4 byte가 아니다.Empty e; 를 통해 인스턴스를 독립적으로 요구한다면, 이러한 “공백” 객체에 컴파일러는 char한 개(1 byte)를 슬그머니 끼어넣는다.HoldsAnInt클래스를 4 byte에 맞춰 byte alignment까지 하면 총 5 byte → 8 byte 라는 결과가 나오게 된다.class Empty {};
class HoldsAnInt : private Empty {
int x;
};
int main() {
printf("%d byte\n",sizeof(HoldsAnInt));
};
그래서 위와 같이 구현하면 총 4 byte가 나오게 된다.
위 예제에서는 Empty클래스가 비어있어서 아무 의미도 없어 보이지만, 사실 공백클래스는 진짜 비어있는 클래스는 아니다.
private 상속을 정당화 할 수 없다는 것이다.private 상속이 구현되지 않은것만 봐도 답이 나온다.