- class Person { //... };
- class Student : private Person { //... };
-
- void eat(const Person& p);
- void study(const Student& s);
-
- void main()
- {
- Person p;
- Student s;
- eat(p);
- eat(s);
- }
-
这个时候,eat(s)就会报错,提示不允许对不可访问的基类Person进行转换。所以,可以看出,如果采用 private 继承:
1)编译器不会自动将一个 derived class 转换为一个 base class;
2)原来在 base class 中的所有 protected or public 成员,被 private 继承到 derived class 后,在derived class 中都会变成 private 属性;
前面一个条款说到复合(composition)也有 is-implemented-in-terms-of 的含义。那就是是选用 composition 还是 private 继承呢?答案是尽量使用 compostion,必要时才使用 private 继承。必要的情况有两种:
1)当有 protected 成员或者 virtual 函数牵扯进来时
假设现在有一个Widget类,我们需要让这个类记录每个成员函数的被调用次数,然后在运行期间周期性的审查记录的信息。为此,我们需要设定一个计时器。而且我们发现在现有的库函数中,Timer class 可以满足需求:
- class Timer
- {
- public:
- explicit Timer(int tickFrequency);
- virtual void onTick() const;
- //...
- };
其中的 virtual onTick() 函数就定义的时钟每滴答一次,就需要执行的动作,非常符合我们的需求。此时,为了让 Widget 类重写 Timer 内的 virtual 函数,Widget 必须继承 Timer。public 继承在此时并不恰当,因为 Widget is not a Timer。采用 public 继承,会让 Widget 继承 Timer 的不必要的接口,容易造成客户不正确使用 Widget 接口。所以此时,我们必须以 private 形式继承 Timer:
- class Widget : private Timer
- {
- private:
- virtual void onTick() const;
- };
通过 private 继承,既能够重新定义 virtual onTIck() 函数,又能够使得原有的Timer class 中的 public 接口隐藏起来,不对客户开放,保持了接口的不变性。
上面的设计已经可以较好的达到功能需求,但是别忘了,我们说过,如果 private 继承非必要,尽量采用 composition 的方式实现。对于上面的例子,也可以采用 composition 方式实现:
- class Widget
- {
- private:
- class WidgetTimer : public Timer
- {
- public:
- virtual void onTick() const;
- //...
- };
-
- WidgetTimer timer;
- //...
- };
这种设计虽然复杂一些,但是可以有两个理由让你选择 composition 方式:
2)面对大小为零的独立对象时
如果现在有一个不含有数据成员的 empty class 和一个含有它的其他类:
- class Empty { };
- class HoldsAnInt
- {
- private:
- int x;
- Empty e;
- };
你会发现,sizeof(HoldsAnInt) > sizeof(int),一个不含有数据成员的类居然还是需要内存;这是因为对于大多数编译器,在面对大小为 0 的独立(非附属)对象时,C++ 官方要求在背后安插一个 char 类型数据到空对象中。所以你会发现,sizeof(Empty) = 1。同时呢,由于对齐要求,HoldsAnInt 类还会加上一些 padding,导致最后 sizeof(HoldsAnInt ) 可能就等于 8 了。
但是,如果你继承一个 empty class,上面所说的就不成立了:
- class HoldsAnInt : private Empty
- {
- private:
- int x;
- };
这个时候,几乎就可以确定 sizeof(HoldsAnInt) == sizeof(int),这就是所说的 emty base optimization。如果你比较在意空间,这种 private 继承方式可能就是一种更好的选择。