明智而审慎地使用private继承——条款39

        条款32曾经论证过C++如何将public继承视为is-a关系。在那个例子中我们有个继承体系,其中class Student以public形式继承class Person,于是编译器在必要时刻(为了让函数调用成功)将Students暗自转换为Persons。现在我们再重复该例的一部分,并以private继承替换public继承:

class Person { ... };
class Student:private Person { ... };    // 这次改用private继承
void eat(const Person& p);               // 任何人都会吃
void study(const Student& s);            // 只有学生才到校学习

Person p;                       // p是人
Student s;                      // s是学生

eat(p);                         // 没问题,p是人
eat(s);                         // 错误!吓,难道学生不是人?!

        显然private继承并不意味is-a关系。那么它意味什么?

        如果classes之间的继承关系是private,编译器不会自动将一个derived class对象(例如Student)转换为一个base class对象(例如Person)。这和public继承的情况不同。这也就是为什么通过s调用eat会失败的原因。第二条规则是,由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:因为它们都只是实现枝节而已)。借用条款34提出的术语,private继承意味只有实现部分被继承,接口部分应略去。如果D以private形式继承B,意思是D对象根据B对象实现而得,再没有其他意涵了。Private继承在软件“设计”层面上没有意义,其意义只及于软件实现层面。

        Private继承意味is-implemented-in-terms-of(根据某物实现出),这个事实有点令人不安,因为条款38才刚指出复合(composition)的意义也是这样。你如何在两者之间取舍?答案很简单:尽可能使用复合,必要时才使用private继承。何时才是必要?主要是当protected成员和/或 virtual函数牵扯进来的时候。

        考虑一个实例,修改Widgets class,让它记录每个成员函数的被调用次数。先找到如下class:

class Timer {
public:
	explicit Timer(int tickFrequency);
	virtual void onTick() const;        // 定时器每滴答一次,此函数就自动调用一次
	...
};

        为了让Widget重新定义Timer内的virtual函数,Widget必须继承自Timer。但public继承在此例并不适当,因为Widget并不是个Timer。是呀,Widget客户总不该能够对着一个Widget调用onTick吧,因为观念上那并不是Widget接口的一部分。如果允许那样的调用动作,很容易造成客户不正确地使用Widget接口,那会违反条款18的忠告:“让接口容易被正确的使用,不易被误用”。在这里,public继承不是个好策略。

        我们必须以private形式继承Timer:

class Widget: private Timer {
private:
	virtual void onTick() const;    // 查看Widget的数据...等等。
	...
};

        这是个好设计,但不值几文钱,因为private继承并非绝对必要。如果我们决定以复合(composition)取而代之,是可以的。只要在Widget内声明一个嵌套式private class,后者以public形式继承Timer并重新定义onTick,然后放一个这种类型的对象于Widget内。下面是这种解法的草样:

class Widget {
private:
	class WidgetTimer: public Timer {
		virtual void onTick() const; 
		...
	};
	WidgetTimer timer;
	...
};

                                                  

        这个设计比只使用private继承要复杂一些些,因为它同时涉及public继承和复合,并导入一个新class(WidgetTimer)。坦白说,我展示它主要是为了提醒你,解决一个设计问题的方法不只一种,而训练自己思考多种做法是值得的(看看条款35)。尽管如此,我可以想出两个理由,为什么你可能愿意(或说应该)选择这样的public继承加复合,而不是选择原先的private继承设计。

        首先,你或许会想设计Widget使它得以拥有derived classes,但同时你可能会想阻止derived classes重新定义onTick。如果Widget继承自Timer,上面的想法就不可能实现,即使是private继承也不可能。(还记得吗,条款35曾说derived classes可以重新定义virtual函数,即使它们不得调用它。)

        第二,你或许会想要将Widget的编译依存性降至最低。如果Widget继承Timer,当Widget被编译时Timer的定义必须可见,所以定义Widget的那个文件恐怕必须#include Timer.h。但如果WidgetTimer移出Widget之外而Widget内含指针指向一个WidgetTimer,Widget可以只带着一个简单的WidgetTimer声明式,不再需要#include任何与Timer有关的东西。对大型系统而言,如此的解耦可能是重要的措施。关于编译依存性最小化,详见条款31

        大多数继承相当于is-a,这是指public继承,不是private继承。复合和private继承都意味is-implemented-in-terms-of,但复合比较容易理解,所以无论什么时候,只要可以,你还是应该选择复合。

请记住

  • Private继承意味is-implemented-in-terms-of(根据某物实现出)。它通常比复合(composition)的级别低。但是当derived class需要访问protected base class的成员,或需要重新定义继承而来的virtual函数时,这么设计是合理的。
  • 和复合(composition)不同,private继承可以造成empty base最优化。这对致力于“对象尺寸最小化”的程序库开发者而言,可能很重要。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值