《Effective C++》学习笔记——条款39
2016-03-02 00:16
316 查看
六、继承与面向对象设计
条款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继承支柱
[b]用 复合 而非 private继承[/b]
比如,我们程序中用到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声明式。
[b]用 private继承 而非 复合[/b]
之前有谈到过,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基类最优化。这对致力于”对象尺寸最小化“的程序库开发者而言,可能很重要。
相关文章推荐
- c++ primer 第五版 笔记前言
- 那些年,我还在学习C# 学习笔记续
- Ruby 魔法 学习笔记之一
- sqlserver 数据库学习笔记
- CSS学习笔记Padding 属性中参数的定义与使用
- prototype 1.5 & scriptaculous 1.6.1 学习笔记
- prototype 学习笔记整理
- Oracle学习笔记(六)
- 关于SQLServer2005的学习笔记 XML的处理
- Jquery 基础学习笔记
- ExtJs 学习笔记基础篇 Ext组件的使用第1/2页
- linux Shell学习笔记第五天
- Jquery 学习笔记(二)
- PHP入门学习笔记之一
- 那些年,我还在学习C# 学习笔记
- Ruffy javascript 学习笔记
- JavaScript 学习笔记(十六) js事件
- JavaScript 学习笔记(十二) dom
- JavaScript 学习笔记(十一)
- JavaScript 学习笔记 Black.Caffeine 09.11.28