若class之间的继承关系是private,则编译器不会自动将一个derived class对象转换为一个base class对象。
由private base class继承而来的所有成员,在derived class中都会变成private属性,纵使它们在base class中原本是protected或public属性。
private继承意味implemented-in-terms-of。
若你让class D以private形式继承class B,你的用意是为了采用class B内已经备妥的某些特性,不是因为B对象和D对象存在有任何观念上的关系。private继承存粹只是一种实现技术(这就是为什么继承自一个private base class的每样东西在你的class内部都是private,因为它们都只是实现枝节而已)。
private继承意味只有实现部分被继承,接口部分应略去。
若D以private形式继承B,意思是D对象根据B对象实现而得,再没有其他含义了。private继承在软件“设计”层面上没有意义,其意义只及于软件实现层面。
private意味implemented-in-terms-of,但上篇文章指出复合的意义也是这样。
该如何取舍?
主要是当protected成员和/或virtual函数牵扯进来的时候。还有一种情况,当空间方面的利害关系足以踢翻private继承的支柱时。
假设一个程序设计Widget,而我们决定应该较好地了解如何使用Widget。
例如我们不只想知道Widget成员函数多么频繁被调用,也想知道经过一段时间后调用比例如何变化。
我们决定修改Widget class,让它记录每个成员函数的被调用次数。运行期间我们将周期性地审查那份信息,也许再加上每个Widget的值,以及我们需要评估的任何其他数据。
为完成这项工作,我们需要设定某种定时器,使我们知道收集统计数据的时候是否到了:
class Timer {
public:
explicit Timer(int tickFrequency);
//定时器每滴答一次,此函数就调用一次
virtual void onTick() const;
//...
};
为了让Widget重新定义Timer内的virtual函数,Widget必须继承自Timer。但public继承在此例并不适当,因为Widget并不是个Timer。因此,必须以private形式继承Timer:
class Widget : private Timer {
private:
virtual void onTick() const;//查看Widget的数据等
//...
};
但private并非绝对必要。若我们决定以复合取而代之,只要在Widget内声明一个嵌套式priavte class,后者以public形式继承Timer并重新定义onTick,然后放一个这种类型对象于Widget内。
下面是这种解法的草样:
class Widget{
private:
class WidgetTimer : public Timer {
public:
virtual void onTick() const;
//...
};
WidgetTimer timer;
//...
};
这个设计比只使用private继承要复杂一些些,因为它同时涉及public继承和复合,并导入一个新class(WidgetTimer)。
在这里展示它主要是为了提醒你,解决一个设计问题的方法不只一种,而训练自己思考多种做法是值得的。
可以想出两个理由,为什么你可能愿意(或说应该)选择这样的public继承加复合,而不是选择原先的private继承设计。
第一,你或许会想设计Widget使它得以拥有derived class,但同时你可能会想阻止derived class重新定义onTick。
若Widget继承自Timer,上面的想法就不可能实现,即使是private继承也不可能。但若WidgetTimer是Widget内部的一个private成员并继承Timer,Widget的derived class将无法取用WidgetTimer,因此无法继承它或重新定义它的virtual函数。
第二,你或许会想要将Widget的编译依存性降至最低。
若Widget继承Timer,当Widget被编译时Timer的定义必须可见,所以定义Widget的那个文件恐怕必须#include Timer.h。但若WidgetTimer移出Widget之外而Widget内含指针指向一个WidgetTimer,Widget可以只带着一个简单的WidgetTimer声明式,不再需要#include任何与Timer有关的东西。
对大型系统而言,如此的解耦可能是重要的措施。
上文提过,有一种激进情况设计空间最优化,可能会促使你选择“private继承”而非“继承加复合”。
这个激进情况只适用于你所处理的class不带任何数据时。
这样的class没有non-static成员变量,没有virtual函数(因为这种函数的存在会为每个对象带来一个vptr),也没有virtual base class(因为这样的base class也会招致体积上的额外开销)。于是这种所谓的empty class对象不使用任何空间,因为没有任何隶属对象的数据需要存储。然而由于技术上的理由,C++裁定凡是独立(非附属)对象都必须有非零大小。
所以若你这样做:
class Empty{};//没有数据,所以其对象应该不使用任何内存
class HoldsAnInt {//应该只需要一个int空间
private:
int x;
Empty e;//应该不需要任何内存
};
你会发现sizeof(HoldsAnInt)>sizeof(int)。一个Empty成员变量竟然要求内存。
在大多数编译器中sizeof(Empty)获得1,因为面对“大小为零的独立(非附属)对象”,通常C++官方勒令默默安插一个char到空对象内。然而,齐位需求可能造成编译器为类似HoldsAnInt这样的class加上一些衬垫,所以有可能HoldsAnInt对象不只获得一个char大小,也许实际上被放大到足够又存放一个int。
但这个约束不适用于derived class对象内的base class成分,因为它们并非独立。若你继承Empty,而不是内含一个那种类型的对象:
class HoldsAnInt :private Empty{
private:
int x;
};
几乎可以确定sizeof(HoldsAnInt)==sizeof(int)。这是所谓的EBO(empty base optimization;空白基类最优化)。EBO一般只在单一继承(而非多重继承)下才可行,统治C++对象布局的那些规则通常表示EBO无法被施行于“拥有多个base”的derived class身上。
现实中的"empty" class并不真的是empty。虽然它们从未拥有non-static成员变量,却往往内含typedef,enum,static成员变量,或non-virtual函数。STL就有许多技术用途的empty class,其中,内含有用的成员(通常是typedef),包括base class,unary_function和binary_function,这些是“用户自定义的函数对象”通常会继承的class。
尽管如此,大多数class并非empty,所以EBO很少成为private继承的正当理由。更进一步说,大多数继承相当于is-a,这是指public继承,不是private继承。复合和private继承都意味is-implemented-in-terms-of,但复合比较容易理解,所以无论什么时候,只要可以,你还是应该选择复合。
当你面对“并不存在is-a关系”的两个class,其中一个需要访问另一个的protected成员,或需要重新定义其一或多个virtual函数,private继承极有可能成为正统设计策略。
即便如此,你也已经看到,一个混合了public继承和复合的设计,往往能够释出你要的行为,尽管这样的设计有较大的复杂度。
“明智而审慎地使用private继承”意味,在考虑过所有其他方案之后,若仍然认为private继承是“表现程序内两个class之间的关系”的最佳方法,这才用它。
总结
1.private继承意味is-implemented-in-terms-of。它通常比复合的级别低。但当derived class需要访问protected class的成员,或需要重新定义继承而来的virtual函数时,这么设计是合理的。
2.和复合不同,private继承可以造成empty base最优化。这对致力于“对象尺寸最小化”的程序开发者而言,可能很重要。