private继承的特性
private继承的特性可以概括为如下两条:
- 如果classes之间的继承关系是private,编译器不会自动将一个derived class对象转换为一个base class对象。
- 由private base class继承而来的所有成员,在derived class中都会变成private属性,纵使它们原本是protected或者public.
private继承的意义
private继承意味着implemented-in-terms-of。
如果我们让class d以private形式继承class b,这说明我们仅仅只是为了采用某些b中已经存在的特性,而不是b和d有任何观念上的交集。
private继承完全是一种实现技术(这也是为何b中所有东西都被转为了private,因为它们都是实现细节)。如果说public继承代表了接口必须被继承,private继承意味着只有实现部分被继承,接口部分则被隐藏。private继承在软件设计层毫无意义,它的价值体现在软件实现层。
何时使用private继承
上一节说过,composition也有implemented-in-terms-of的意义,那如何在composition与private继承之间取舍呢?答案是:尽可能使用复合,只有在必要时(protected成员以及virtual函数牵扯进来)才使用private继承。
问题实例
假设我们有一个Widget,我们需要令它能够记录每一个成员函数的被调用次数,并且在运行期间我们将周期性地检查被调用次数。为了完成这项工作,我们需要一个定时器。假定库中存在着Timer class:1
2
3
4
5
6class Timer{
public:
explicit timer(int tickFrequency);
virtual void onTick() const;//时间到后调用该函数
...
}
看起来我们只需要重新定义那个virtual函数就可以指定周期地读取Widget的状态了。
解决方案
privte继承
如果需要重定义virtual函数,Widget必须继承自Timer。它们之间的继承关系肯定不是public继承,因为它们并不满足is-a关系,而且用户也不能对着一个widget对象调用onTick成员函数,因为从观念上而言,该接口并不是Widget的一部分。(接口易用性原则 Effective C++ 18)
所以我们使用了private继承,并且写作如下形式:1
2
3
4class Widget:private Timer{
private:
virtual void onTick() const;//重定义函数 读取内部数据
}
这个设计很好,但是并不值钱,因为private继承绝非必要。我们完全可以使用一个composition来取代他。
composition
只要在widget内部声明一个嵌套式private class,后者以public形式继承Timer并且重新定义onTick,最后放一个此类对象在该widget内即可,有设计样稿如下:1
2
3
4
5
6
7
8class Widget{
private:
class widgetTimer:public Timer{
public:
virtual void onTick() const;
};
widgetTimer Timer;
};
composition写法的优点
- 未来时态编程兼容性(More Effective C++ 32)
未来Widget可能会派生处derived class,但同时你可能会禁止derived class重新定义ontick。如果Widget继承自Timer,以上做法绝无可能实现。(derived class总可以重定义virtual函数)
如果Widgettimer是Widget的一个private成员并且继承自Timer,Widget的derived class将无法直接使用WidgetTimer,因此也无法继承或者定义它的virtual函数。(阻止derived class重新定义virtual函数的能力,可见Java与C++中的final) - 降低编译依存性
如果Widget继承自Timer,那么它必然需要#include Timer.h
。
但如果现在将WidgetTimer移出Widget之外,然后令Widget内含一个指针指向WidgetTimer,(PIMPL)Widget就可以只带着一个简单的声明式,这种解耦对于大型系统而言极为重要。
总之,private继承主要用于“当一个意欲成为dc的class想访问一个意欲成为bc的class的protected部分,或者为了重定义一个或者多个virtual函数”,但此时两个class之间的关系决非is-a而是is-implementef-in-terms-of的关系。
private继承与empty class
private继承可以帮助实现空间最优化,不过只涉及empty class.
empty class
empty class是指不带有任何数据的class,它们没有non-static成员变量,没有virtual函数(因为会导致vptr),没有virtual base class(亦会造成空间额外开销,可见Effective C++ 41、More Effective C++ 24).
从理论上而言,empty class对象不使用任何空间,因为没有任何数据需要存储。但是由于技术上的理由,C++规定凡是独立对象都必须拥有非0大小,1
2
3
4
5
6class Empty {};
class HoldAnInt{
private:
int x;
Empty e;//理论上不占据内存
}
你会发现sizeof(HoldAnInt)
>sizeof(int)
,因为sizeof(Empty)
=1.
由于“独立对象都必须拥有非0大小”的规定,编译器将一个char进入了空对象内。而后可能存在的齐位等要求导致了Holdanint可能获得了不止一个char的大小。
EBO(empty base optimization)
之前我们说过,独立对象的大小一定不为0,也就是说,这个约束不适用于对象内的base class成分,所以如果令Holdanint继承自Empty,则有1
2
3
4
5class HoldAnInt:private Empty{
private:
int x;
}
sizeof(HoldAnInt)==sizeof(int);//true
这也就是所谓的EBO(empty base optimization).此外,EBO只在单一继承时有效。
EBO的作用
可能有人会质疑Empty的作用,实际上在STL中存在着大量的类似Empty的基类,例如unary_function等等(详见Effective STL 40),它们的内部往往内含着有用的typedefs、enums、以及non-virtual函数。正是由于EBO的存在,这些函数保证了derived class的大小不受干扰。
总结
- private继承意味着is-implemented-in-terms-of。它通常比composition的级别低,但是如果dc想要访问base class的protected成员或者重定义virtual时,这种设计是合理的
- private继承可以实现EBO,这对需要尽力缩减对象大小的开发者来说是一件好事。