《Effective C++》学习笔记——条款39
来源:互联网 发布:三菱伺服选型软件 编辑:程序博客网 时间:2024/05/26 02:51
六、继承与面向对象设计
条款39、明智而审慎的使用private继承
is-a? No!
在之前的条款32中,我们讨论了public继承,它是一个 is-a 关系。
此处,我们继续用那个例子…的一部分,进而阐述此条款
class Person { ... };class Student: private Person { ... };void eat(const Person& p);void study(const Student& s);Person p;Student s;eat(p);eat(s);
在这个例子中,我们用private继承。
当我们调用 eat(s) 时发现,会报错,所以,显然 private继承并不代表 is-a 关系。
因为,派生类对象不会被转换为基类对象;而且 派生类从基类继承而来的所有成员,都将成为private的形式。
implemented-in-terms-of 根据某物实现出
那private继承意味着什么呢? 在上一个条款也浅谈过,private继承实际上意味着 implemented-in-terms-of,就是 根据某物实现出。
- 我们让一个派生类,private形式继承基类,是为了采用基类的某些特性,并不是说它俩之间有什么关系。
- private继承 在设计层面上没有意义,只在软件实现层面上有意义。
private继承 与 复合
上一个条款刚指出,复合 其中一个意义也是 根据某物实现出。
对于这两者,有一个原则:
- 尽可能的使用复合,必要时使用private继承
那么,什么时候算是必要的情况呢?
- 当 protected 成员 或 virtual函数 相关
- 当空间方面的利害关系足以踢翻private继承支柱
用 复合 而非 private继承
比如,我们程序中用到Widget类,我想知道Widget成员函数的使用次数,所以,我就要修改一下Widget类,让它记录每个成员函数的调用次数。
我们可以用timer类,因为定时器每滴答一次,就会调用里面的onTick函数,我们就可以重定义那个函数。
1.我们可以用private继承 来实现
class Widget : private Timer {private: virtual void onTick() const; // 实现想要的操作 ...};
通过private继承,Timer的public函数 onTick在 Widget类内就成了private,我们重新声明并定义它。
2.用 复合 方式来实现
class Widget {private: class WidgetTimer:public Timer { public: virtual void onTick() const; ... }; WidgetTimer timer; ...};
在Widget类内声明了一个嵌套是private类,这个类以public形式来继承Timer并重定义onTick方法,然后在Widget类放该类的一个对象。
为什么用复合 优于 private继承 呢?
- 首先,我们想设计Widget使它拥有派生类,但同时又不想派生类重定义onTick。
如果直接继承,肯定无法这样实现;但是,我们可以在类内实现这样的东西,像上面的例子一样。 - 其次,要将Widget的编译依存性降至最低。如果Widget直接继承Timer,当Widget被编译时,Timer的定义必须可见,所以Timer定义 也需要包含进来;但,如果Widget类内内含一个指向WidgetTimer的指针,就可以只带着一个简单的WidgetTimer声明式。
用 private继承 而非 复合
之前有谈到过,private继承主要用于“当一个想成为派生类的类想访问一个想成为基类的protected成分,或为了重新定义virtual函数。
但是,当要处理的类不带任何数据时,为了空间最优化,就应该用private继承,而非 继承+复合。
这样的类没有non-static成员变量,没有virtual函数,也没有virtual base class,这种类的对象不使用任何空间,因为没有该对象的数据存储。
class Empty { }; // 没有数据class HoldsAnInt {private: int x; Empty e;};
理论上来讲,因为Empty类没有任何数据,所以对象应该不使用内存,所以HoldsAnInt类所占用的内存就是 x的 int 所占用的。
但实际上, sizeof(Empty) 将得到1,因为面对 “大小为零之独立对象”,C++ 官方将安装一个char到空对象内。
但并不是所有编译器都放一个char,有些编译器可能放一些其他的,甚至能大到存放一个int。
但是,这个约束不适用于继承,因为当你继承一个Empty类
class HoldsAnInt: private Empty {private: int x;};
这里 HoldsAnInt 的内存占用 与 int 是一样的。这就是 EBO(Empty Base Optimization 空白基类最优化)。
上面所说的都是理想情况,在现实中 所谓的 Empty class 并不是真正的什么都没有,里面常常含有 typedef、enums、static成员变量。
总结一下,大多数类并非empty,所以EBO很少成为private继承的正当理由。大多数继承相当于 is-a,这是指 public 继承; private 继承 与 复合+private继承 都意味着 is-implemented-in-terms-of,但复合比较容易理解,所以只要可以用复合,就应该用复合。
请记住
- Private继承意味 is-implemented-in-terms-of(根据某物实现出)。它通常比复合(composition)的级别低。但是当派生类需要访问protected 基类的成员,或需要重新定义继承而来的virtual函数时,这样的设计是合理的。
- 和 复合 不同,private继承可以造成empty基类最优化。这对致力于”对象尺寸最小化“的程序库开发者而言,可能很重要。
- 《Effective C++》学习笔记——条款39
- 《Effective C++》学习笔记——条款15
- 《Effective C++》学习笔记——条款16
- 《Effective C++》学习笔记——条款17
- 《Effective C++》学习笔记——条款18
- 《Effective C++》学习笔记——条款19
- 《Effective C++》学习笔记——条款20
- 《Effective C++》学习笔记——条款21
- 《Effective C++》学习笔记——条款22
- 《Effective C++》学习笔记——条款23
- 《Effective C++》学习笔记——条款24
- 《Effective C++》学习笔记——条款25
- 《Effective C++》学习笔记——条款26
- 《Effective C++》学习笔记——条款27
- 《Effective C++》学习笔记——条款28
- 《Effective C++》学习笔记——条款29
- 《Effective C++》学习笔记——条款30
- 《Effective C++》学习笔记——条款31
- python程序运行总是有invalid syntax 错误 如何解决?
- 算法:回溯算法之floodfill
- Kernel Live-patching (by quqi99)
- java项目命名规范
- Jfinal进阶系列之初体验
- 《Effective C++》学习笔记——条款39
- C++ 红黑树
- Multism14 下载及安装
- iOS 之 OBJECTC 调用C、OBJECTC调用C++
- 【PAT】1035. 插入与归并(25)
- Nginx反向代理上传大文件报错(failed to load resource : net :: ERR_CONNECTION_RESET)
- Git
- 初学Phaser.js之碰撞检测
- LeetCode:2 Add Two Numbers