您的位置:首页 > 其它

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

2009-08-20 10:51 204 查看
条款39:明智而审慎地使用private继承
Use private inheritance judiciously.
内容:
前面的条款中我们谈到将public继承视为一种is-a关系,在这里你不免有产生一个疑问:那对于private继承,我们应该视为什么关系呢?我们来看下面这个测试:
class Person{...};
class Student:private Person{...};
void eat(const Person& p);
void study(const Student& s);
Person p; //p is a person
Student s; // s is a student.
eat(p); //ok,p is a person,can eat something
eat(s); //damned! it's surprise ! isn't student person?????
从上面的测试我们可以看出来,对于private继承,我们不能仅仅地将它视为is-a关系,那这种继承关系到底我们如何看待它,在软件设计的层面上有什么意义?带着这些问题,我们继续往下看.
private继承其实意味着implemented-in-terms-of(根据某物实现出).如果你写的class Derived以private继承自class Base,你的用意是为了让Dervived类采用到Base类中的某些特性,而不是因Dervied对象与Base对象存在一些观念上的关系.其实到这里我们应该看的出来:private继承纯粹只是一种实现的技术而已.这种继承关系在软件"设计"层面上没用意义,其意义只在于其实现层面.
既然private继承意味着is-implemented-in-term-of(根据某物来实现),而前面一款我们也提到了复合(composition)的意义也是如此,那在实际的运用当中,这两者之间做如何取舍呢?我们的答案是这样的:尽可能使用复合,必要时才使用private继承.必要的时候??什么时候才是你说的"必要的时候"?主要就是当protected成员和/或virtual函数牵扯进来的时候.
为了让你更加理解上面我们所说的,我们来看下面这个具体的例子.这个例子涉及到Widget类,我们现在不仅想直到该类的成员函数多么频繁地被调用,而且我们也想了解一段时间以后的调用比例的变化.于是我们决定记录Widget每个成员函数的被调用次数.为了实现这项功能,我们需要设定某种定时器.为了避免写新代码,我在我的百宝箱里翻箱倒柜地捣鼓了半天,终于开心地发现了这个Timer:
class Timer{
public:
explicit Timer(int tickFrequency);
virtual void onTick()const; //定时器每滴答一次,此函数就被自动调用一次.
...
};
this is what we want!一个Timer对象,我们可以调整任何频率滴答前进,每次滴答就调用我们重新定义的virtual函数,让其取出Widget的当时状态.That's perfect!为了让Widget重新定义virtual函数,将Widget继承自Timer貌似是一个不错的设计,要是将它们之间的继承关系定性为public继承,显然是不合适的(public继承是一种is-a的关系).这里我们必须用private继承:
class Widget:private Timer{
private:
virtual void onTick()const; //查看Widget状态...
...
};
这样的设计是很好,但是并非绝对必要,我们用复合照样能实现,看下面的另一种设计:
class Widget{
private:
class WidgetTimer:public Timer{
public:
virtual void onTick()const;
...
};
WidgetTimer timer_;
...
};
这样的设计比上一个好像复杂了点,但是它多了上一个设计没用的优点:
第一,你可能想让Widget拥有derived class,但同时呢,你又不想让derived class重新定义onTick函数,显然如果Widget继承自Timer,这个想法就不能实现,即使是private继承也不可能(条款35中提到derived可以重新定义virtual函数,即使不得调用它). 当若用复合实现的话,内部的private成员继承Timer,Widget的derived classes将无法取用WidgetTimer,因此无法继承它或重新定义virtual函数.
第二,你可能会想将Widget的编译依赖性降至最低.如果Widget继承Timer,当Widget被编译时Timer的定义必须可见,所以定义Widget的那个文件恐怕必须#include Timer.h,如果WidgetTimer之外而Widget内含一个指向WidgetTimer,Widget就可以只带一个
WidgetTimer的声明式,不再需要include任何与Timer有关的东西.
既然复合的优点比private继承多出这么多的优点,那么我们是不是应该舍弃private继承而全部用复合来代替呢?答案是否定的,我们来看一下激进的情况,下面有一个空类empty与HoldsAnInt类:
class Empty{};//啥都没用
class HoldsAnInt{
private:
int x_;
Empty empty_;
};
你会发现sizeof(HoldsAnInt) > sizeof(int),原因很简单Empty类,并不是"空"类,其实在大多数编译器中,sizeof(Empty)获得1.通常是C++官方规定默默安插一个char到空对象内.然而aligment可能造成编译器为类似HoldsAnInt这样的class加上一些衬垫,故可能HoldsAnInt对象不仅获得一个char大小,也许实际上被放大到足够存放一个int.那我再用private继承来看看:
class HoldsAnInt:private Empty{
private:
int x_;
};
得到的结果 sizeof(HoldsAnInt) == sizeof(int).这就是所谓的空白最优化-EBO(empty base optimization),我所试过的所有编译器都有这样的结果.如果你的客户很在意空间,那么你就得注意一下EBO.注意EBO只在单一继承(非多重继承)下才可行.EBO无法被施行于"拥有多个base"的dervied classes身上.
大多数classes并非empty.所以EBO很少成为private继承的正当理由.大多数继承相当于is-a,这里指public继承而不是private继承.复合和private继承都意味着is-implemented-in-terms-of,但复合比较容易理解,所以无论是么时候,只要可以,你还是应该选择复合.当你面对"并不存在is-a关系"的两个classes,其中一个需要访问另一个protected成员,或需要重新定义一个或多个virtual函数,private继承极有可能成为正统设计策略.
请记住:
■ Private继承意味着is-implementd-in-terms-of(根据某物实现出).它通常比复合(composition)的级别低.当derived class需要访问protected base class的成员,或需要重新定义继承而来的virtual函数时候,这么设计是合理的.
■ 和复合(compoistion)不同,private继承可以造成empty base最优化.这对置于"对象尺寸最小化"的程序库开发者而言,可能很重要.
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: