一、先介绍一下private继承的语法
- 某派生类private继承于基类之后:
- ①基类中的所有内容(不论是public、protected、private)在派生类中都是不可访问的
- ②不能再将派生类对象转换为基类对象
- ③基类仍然可以重写/隐藏基类的成员方法
二、private继承意味着什么关系?
- private继承意为implemented-terms-of(根据某物实现出):
- 假设你让class D以private继承于class B,用意为采用class B内的某些特性来实现class D,再无其他意义了
- 借助条款34的属于:private继承意味只有实现部分(也就是基类中已经实现的函数)被继承,接口部分(基类中只定义还未实现的)应该被省去
- 与类的复合模式的关系:
- 在条款38中介绍了类的复合(composition)模式,其中类的复合也有着“is-implemented-in-terms-of”的意义
- 两者有着同样的意义,但是建议:尽可能使用复合,必要时才使用private继承
- 何时才必要使用private呢?
- 主要是当protected成员和/或virtual函数牵扯进来的时候。当派生类想要访问基类的protected成分或者基类想要重写一个或多个virtual函数
- 还有一种情况,就是当空间方面的利害关系足以踢翻private继承的支柱时(下面介绍)
三、演示案例
- 假设我们有这样一个需求:
- 我们有一个类Widget,现在想要知道Widget成员函数被调用的次数
- 现在我们绝对修改Widget class,让它记录每个成员函数被调用次数
- 为了完成这项工作,我们定义了下面的定时器,该定时器每隔一段时间就会对数据进行统计(统计每个成员函数被调用的次数)
class Timer {
public:
explicit Timer(int tickFrequency);
virtual void onTick()const; //定时器每滴答一次,此函数就被自动调用一次
};
- 现在我们需要:
- Timer类会根据设置的频率进行滴答前进,每次滴答就调用virtual函数
- 现在我们需要Widget类继承于Timer,然后重新定义这个virtual函数,然后使用该函数来统计Widget的数据(统计每个成员函数被调用的次数)
①错误的做法:以public方式继承
- 错误的做法是让Widget以public方式继承于Timer,然后重写其virtual函数
class Timer {
public:
explicit Timer(int tickFrequency);
virtual void onTick()const; //定时器每滴答一次,此函数就被自动调用一次
};
class Widget :public Timer {
public:
virtual void onTick()const;
};
- 为什么错误:
- 类似于is-a的关系:我们知道Widget不是一个Timer,我们总不该对一个Widget调用onTick()函数吧?这样会显示很诡异
- 另外,会违反条款18的忠告:让接口容易被正确使用,不易被误用
②以private方式继承
- 为了完成上面的需求,我们可以让Widget以private的方式继承于Timer
- 代码如下:
class Timer {
public:
explicit Timer(int tickFrequency);
virtual void onTick()const; //定时器每滴答一次,此函数就被自动调用一次
};
class Widget :private Timer {
private:
virtual void onTick()const; //查看Widget的数据..等等
};
- 我们在Wiget中重写了onTick()函数,但是将其声明为private的,不能将其声明为public接口,因为如果声明为public,又再次与上面的public继承相类似了
③以复合的形式实现
- 在条款38我们介绍过,复合形式的类也有着“is-implemented-in-terms-of”的意义,因此我们还可以使用复合模式来实现这个功能
- 代码如下:
class Timer {
public:
explicit Timer(int tickFrequency);
virtual void onTick()const; //定时器每滴答一次,此函数就被自动调用一次
};
class Widget{
public:
private:
class WidgetTimer :public Timer {
public:
virtual void onTick()const;
};
WidgetTimer timer;
};
- 我们派生了一个Timer的派生类WidgetTimer,并重写onTick()函数,然后定义一个WidgetTimer类对象定义于Widget class中
- 相同的问题,建议使用复合模式,而不建议使用private继承,原因有两点:
- ①防止Widget的派生类重写onTick()函数:
- 在继承方式下:如果Wiget又定义了派生类,你不希望派生类去重写onTick()函数,但是这种情况可能会无法阻止
- 在复合模式下:Widget的派生类就不可能有机会去重写onTick()函数了,因为WidgetTimer类是Widget内部的一个private成员,派生类永远无法访问
- ②可以将Widget的编译依存性降至最低:
- 在继承方式下:如果Widget继承与Timer,那么当Widget被编译时需要知道Timer的定义(不仅仅是声明),因此你可能会在Widget的头文件中包含#include "Timer.h"这样的东西
- 在复合模式下:假设我们修改上面的复合模式,将WidgetTimer定义在Widget之外,然后在Widget内定义一个WidgetTimer的指针,此时Widget可以只带着WidgetTimer的声明式,那么当Widget编译时就不需要任何与Timer的任何东西。对大型系统而言,这是很重要的措施
四、private继承的另外一个使用场景
- private继承还用于:基类的class不带有任何数据时
- 基类通常:没有non-static成员变量,也没有virutal函数(因为这种函数的存在会为每一个对象带来一个vptr,见条款7),也没有virtual base classes(因为这样的base classes也会导致类的体积增大,见条款40)
演示案例
- 当一个类没有任何成员变量时,编译器会自动为其设定大小(不同的编译器不同),通常为1字节(C++默认安插一个char到空类中)。例如:
class Empty {}; //空类
sizeof(Empty); //1字节
- 上面的规则是适用于独立的类,如果其有派生类,并且派生类有成员,那么这种规则就会消失。例如:
class Empty {};
class HoldsAnint :private Empty {
private:
int x;
};
sizeof(HoldsAnint); //4
- 这个规则就是所谓的EBO:空白积累最优化(empty base optimization)。如果你的程序非常在意空间,那么值得注意EBO
- 另外还需要注意:EBO一般只在单一继承(而非多重继承下)才可行
- 例如STL有很多实现中就用到了empty classes。例如unary_func和binary_function等。EBO使继承很少增加派生类的大小
五、总结
- private继承意为“is-implemented-in-terms-of(根据某物实现出)”。它通常比复合(composition)的级别低。但是当derived class需要访问protected base class的成员,或需要重新定义继承而来的virtual函数时,这么设计时合理的
- 和复合(composition)不同,private继承可以造成empty base最优化。这对致力于“对象尺寸最小化”的程序库开发者而言,可能很重要