您的位置:首页 > 编程语言 > C语言/C++

《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基类最优化。这对致力于”对象尺寸最小化“的程序库开发者而言,可能很重要。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签:  学习笔记