我们都知道,public继承可视为is-a关系。定义一个继承关系:CStudent: public CPerson。可向一个函数原本类型为CPerson形参传递CStudent对象。代码如下:
class CPerson {......};
class CStudent : public CPerson {.......};
void Eate(const CPerson *pPerson); // 任何人都可执行吃动作
void Study(const CStudent *pStudent); // 任何学生都可以执行学习动作
CPerson damao; // damao 是一个人。
CStudent xiaoming; // xiaoming是一个学生
Eate(&damao); // damao是人,所以可以执行吃动作。
Study(&xiaoming); // xiaoming是学生,所以可以执行学习动作。
Eate(&xiaoming); // xiaoming是学生同样也是人,所以可以执行吃动作。
Study(&damao); // 错误:damao是人,但不是学生,所以不可以执行学习动作。
CStudent隐式转化为了CPerson,这也从一个侧面反映了CStudent is a CPerson的论断,但是如果CStudent和CPerson之间不是public继承关系,而是private继承关系呢?
class CPerson {......};
class CStudent : private CPerson {.......};
void Eate(const CPerson *pPerson); // 任何人都可执行吃动作
void Study(const CStudent *pStudent); // 任何学生都可以执行学习动作
CPerson damao; // damao 是一个人。
CStudent xiaoming; // xiaoming是一个学生
Eate(&damao); // damao是人,所以可以执行吃动作。
Study(&xiaoming); // xiaoming是人,所以可以执行吃动作。
Eate(&xiaoming); // 错误:xiaoming是学生,不是人,不能执行吃动作。
Study(&damao); // 错误:damao是人,但不是学生,所以不可以执行吃学习动作。
通过上面的例子可以看出,显然private继承并不意味着is a。学生不是人了,显然是逻辑上出现了问题,原因为何呢?来看private继承的原理。
private继承原理如下:
(1)采用private继承时,编译器并不会把派生类(CStudent类)转型为基类(CPerson类)。这就是为何Eate(&xiaoming)会失败的原因。
(2)采用private继承时,基类中的所有public、protected成员函数会成为派生类中的private成员。
(3)Private继承是,派生类和基类不是is a关系,而是一种实现关系。
现在,我们明白了上述例子无法运行的原理了。但是private继承意味着什么呢?其实他意味着is -implemented-in-terms-of(是根据……实现的)。也就是说,如果你声明class Drived:private Base。只能说明你对 class Base的某些特性感兴趣,而不是因为在class Drived和class Base的对象之间有什么概念上的关系。
私有继承仅展示的是class Drived依靠class Base而实现这种概念,除此之外别无其他了。private继承意味只有实现部分被继承, 接口部分应略去。如果classes之间的继承关系是private,编译器不会自动将一个derived class对象转换为一个base class对象,而pubic展示的是class Drived是一种class Base。
私有继承的一个经典案例:如果我们需要每隔1ms刷新一下界面或处理一下界面中的数据信息。我们看Effective C++给出的实例代码。
class Timer
{
public:
explicit Timer(int tickFrequency);
virtual void onTick() const; // 每隔tick到达是都会回调此函数的。
...
};
class Widget: private Timer
{
private:
virtual void onTick() const; // 查看Widget的数据,执行界面刷新和数据处理
...
};
通过私有继承,Timer的public onTick 函数在Widget 中变成 private的,将onTick放入公有接口将误导客户认为他们可以调用它,而这违背了使接口应易于正确使用的论述。所以这里必须声明为private。
虽然,私有继承可以解决这类问题。但在这有时候并非必要。你可以通过尽量用复合代替private继承。因为复合可避免virtual函数重定义。我们看复合实现代码:
//复合实现
class Widget
{
private:
class WidgetTimer : : public Timer
{
public:
virtual void onTick() const; // 查看Widget的数据,执行界面刷新和数据处理
};
WidgetTimer timer;
};
最佳实践
在遇到private继承时,请尽量用复合机制替换之。因为这样可以带来两个好处:
- 你可能要做出允许Widget有派生类的设计,但是你还可能要禁止派生类重定义 onTick。如果Widget 从Timer继承,那是不可能的,即使继承是private的也不行。但是组合机制可以。
- 可最小化Widget的编译依赖。
请谨记
- 私有继承意味着is-implemented-in-terms of(是根据……实现的)。它通常比composition(复合)更低级,但当一个 derived class(派生类)需要访问protected base class members(保护基类成员)或需要重定义 inherited virtual functions(继承来的虚拟函数)时它就是合理的。
- 与composition(复合)不同,私有继承能使 empty base optimization(空基优化)有效。这对于致力于最小化 object sizes(对象大小)的库开发者来说可能是很重要的。