C/C++编程:明智而审慎的使用private继承
C/C++编程:确定你的public继承塑模出is-a关系论证了如何将public继承视为is-a关系,这里我们来看一下private继承意味着什么?
class Person{
};
class Student : public Person {
};
void eat(const Person &p){
}; // 任何人都会吃
void study(const Student &s){
}; // 只有学生会学习
int main(){
Person p;
Student s;
eat(p); // 人会吃
eat(s); // error!!! 难道学生不是人???
study(p); // error!!!不是所有的人都会学习
study(s); // 只有学生会学习
}
从上面我们可以看出,private继承绝不是is-a的关系。
又还可以看出,private继承的行为:
- 如果类是private继承,编译期绝不会自动将一个派生类对象转换为一个基类对象
- 由private base class继承而来的所有成员,在派生类中都会变成private属性,即使它们原本在基类中是public或者protected。
好了,现在我们来讨论意义。private继承意味着implemented-in-terms-of(根据某物实现出)。如果你让类D以private形式继承类B,你的用意是为了采用类B内已经备妥的某些特性,不是因为B对象和D对象存在有任何观念上的关系。private继承纯粹是一种实现技术(这就是为什么继承自一个private base class的每样东西在你的类内都是private,因为它们只是实现细节而已)。private继承意味着只有实现部分被继承,接口部分应该略去。如果D以private形式继承B,意思是D对象根据B对象实现而得,没有其他意思。private继承在软件“设计”层面没有意义,其意义只设计软件实现层面。
private继承意味着implemented-in-terms-of,但是C/C++编程:通过复合塑膜出has-a或者”根据某物实现出“指出复合也有”根据某物实现出“这个意义。两者之间如何取舍呢? 答案很简单:尽可能的用复合,必要时采用private。那什么时候是必要时?
- “当派生类想访问基类的protected成分,或者为了重新定义或者或者多个虚函数”
- 当空间方面的厉害关系足以踢翻private继承的支柱时。
看个例子。假设我们的程序涉及widgets,而我们决定应该较好的了解如何使用widgets。比如我们不只想知道widget成员函数多么频繁的被调用,也想知道经过一段时间后调用比例如何变化。
我们决定修改widget类,让它记录每个成员函数的被调用次数。运行期间我们将周期性的审查那份信息,也许再加上每个widget的值,以及我们需要评估的任何其他数据。为了完成这项工作,我们需要设定某种定时器,使我们直到收集统计数据的时候是否到了。
class Timer{
public:
explicit Timer(int tickFrequency);
virtual void onTick() const; // 定时器每嘀嗒一次,此函数就被自动调用一次。
};
上面的Timer对象,可以调整为我们需要的任何频率嘀嗒前进,每次嘀嗒就调用某个虚函数。我们可以重新定义那个虚函数,让后者取出widget的当前状态。
为了让widget重新定义timer内的虚函数,widget必须继承自Timer。但是widget不是timer,所以public继承不适合,我们必须以private形式继承timer
class Widget : private Timer{
private:
virtual void onTick() const; //查看widget的数据等
};
通过private继承,Timer的public onTick()在widget内变成了private,客户就不能调用它了。again,如果是public继承会误导客户端以为它们可以对窗口做时间操作,这是不合适的。
当然也可以用复合来替换private继承(更好):只要在widget声明一个嵌套的private类,后者以public形式继承Timer并重新定义onTick,然后放一个这样的类在widget内:
class Widget{
private:
class WidgetTimer : public Timer{
public:
virtual void onTick() const;
};
WidgetTimer timer;
};
为什么public继承+复合比private继承更好呢?
- 首先,你或许会想设计widget使它得以拥有派生类,但同时你可能会想阻止派生类重新定义onTick。如果widget继承自timer,上面的想法就不可能实现,即使是private继承也不可能(派生类可以重新定义virtual函数,即使它们不得调用它)。但如果WidgetTimer 是Widget内部的一个private成员并继承Timer,Widget的派生类将无法取用WidgetTimer ,因此也无法继承它或重新定义它的虚函数。[C++阻止派生类重新定义虚函数的方法]
- 第二,可以将widget的编译依存性将至最低。(这一点我不是很赞同)。
当空间方面的厉害关系足以踢翻private继承的支柱时这句话怎么理解?
当你所处理的类不带任何数据时,这样的类没有non-static成员变量、没有虚函数(这种函数存在会为每个对象带来一个虚函数),没有虚基类(这种类也会带来体积上的额外开销)。;理论上该类的对象不使用任何空间,但是由于技术上的理由,C++规定凡是独立对象必须有非零大小。也就是说空类不空:编译器会默认安插一个char+对齐
class Empty{
};
class HoldsAnInt{
private:
int x;
Empty e;
};
sizeof(HoldsAnInt) > sizeof(int); // true
上面说到,独立
对象的大小一定不为0。也就是说,这个约束不适用于派生类对象的基类成分,因为它们并非独立对象:
class HoldsAnInt: private Empty{
public:
int x;
};
sizeof(HoldsAnInt) == sizeof(int); // true
为什么sizeof(HoldsAnInt) == sizeof(int)
为真呢?因为由EBO(empty base optimization,空基类最优化)。另外,EBO一般只在单一继承(而非多重继承)下才可行,无法施行于拥有多个基类的派生类身上。
总结
- private继承意味着implemented-in-terms-of(根据某物实现出)。它通常比复合的级别低。但是当派生类需要访问protected base class的成员,或者需要重新定义virtual时,这么设计是合理的
- 和复合不同,private继承可以造成empty base最优化,这对致力于“对象尺寸最小化”的程序开发者而言,可能很重要
还没有评论,来说两句吧...