C/C++编程:明智而审慎的使用private继承

妖狐艹你老母 2023-01-20 08:13 225阅读 0赞

C/C++编程:确定你的public继承塑模出is-a关系论证了如何将public继承视为is-a关系,这里我们来看一下private继承意味着什么?

  1. class Person{
  2. };
  3. class Student : public Person {
  4. };
  5. void eat(const Person &p){
  6. }; // 任何人都会吃
  7. void study(const Student &s){
  8. }; // 只有学生会学习
  9. int main(){
  10. Person p;
  11. Student s;
  12. eat(p); // 人会吃
  13. eat(s); // error!!! 难道学生不是人???
  14. study(p); // error!!!不是所有的人都会学习
  15. study(s); // 只有学生会学习
  16. }

从上面我们可以看出,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的值,以及我们需要评估的任何其他数据。为了完成这项工作,我们需要设定某种定时器,使我们直到收集统计数据的时候是否到了。

  1. class Timer{
  2. public:
  3. explicit Timer(int tickFrequency);
  4. virtual void onTick() const; // 定时器每嘀嗒一次,此函数就被自动调用一次。
  5. };

上面的Timer对象,可以调整为我们需要的任何频率嘀嗒前进,每次嘀嗒就调用某个虚函数。我们可以重新定义那个虚函数,让后者取出widget的当前状态。

为了让widget重新定义timer内的虚函数,widget必须继承自Timer。但是widget不是timer,所以public继承不适合,我们必须以private形式继承timer

  1. class Widget : private Timer{
  2. private:
  3. virtual void onTick() const; //查看widget的数据等
  4. };

通过private继承,Timer的public onTick()在widget内变成了private,客户就不能调用它了。again,如果是public继承会误导客户端以为它们可以对窗口做时间操作,这是不合适的。

当然也可以用复合来替换private继承(更好):只要在widget声明一个嵌套的private类,后者以public形式继承Timer并重新定义onTick,然后放一个这样的类在widget内:

  1. class Widget{
  2. private:
  3. class WidgetTimer : public Timer{
  4. public:
  5. virtual void onTick() const;
  6. };
  7. WidgetTimer timer;
  8. };

在这里插入图片描述
为什么public继承+复合比private继承更好呢?

  • 首先,你或许会想设计widget使它得以拥有派生类,但同时你可能会想阻止派生类重新定义onTick。如果widget继承自timer,上面的想法就不可能实现,即使是private继承也不可能(派生类可以重新定义virtual函数,即使它们不得调用它)。但如果WidgetTimer 是Widget内部的一个private成员并继承Timer,Widget的派生类将无法取用WidgetTimer ,因此也无法继承它或重新定义它的虚函数。[C++阻止派生类重新定义虚函数的方法]
  • 第二,可以将widget的编译依存性将至最低。(这一点我不是很赞同)。

当空间方面的厉害关系足以踢翻private继承的支柱时这句话怎么理解?

当你所处理的类不带任何数据时,这样的类没有non-static成员变量、没有虚函数(这种函数存在会为每个对象带来一个虚函数),没有虚基类(这种类也会带来体积上的额外开销)。;理论上该类的对象不使用任何空间,但是由于技术上的理由,C++规定凡是独立对象必须有非零大小。也就是说空类不空:编译器会默认安插一个char+对齐

  1. class Empty{
  2. };
  3. class HoldsAnInt{
  4. private:
  5. int x;
  6. Empty e;
  7. };
  8. sizeof(HoldsAnInt) > sizeof(int); // true

上面说到,独立对象的大小一定不为0。也就是说,这个约束不适用于派生类对象的基类成分,因为它们并非独立对象:

  1. class HoldsAnInt: private Empty{
  2. public:
  3. int x;
  4. };
  5. 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最优化,这对致力于“对象尺寸最小化”的程序开发者而言,可能很重要

发表评论

表情:
评论列表 (有 0 条评论,225人围观)

还没有评论,来说两句吧...

相关阅读

    相关 明智使用Pimpl

    明智地使用Pimpl  首先引用一下别人的内容 pimpl 用法背后的思想是把客户与所有关于类的私有部分的知识隔离开。由于客户是依赖于类的头文件的,头文件中的任何变化都会影