600字范文,内容丰富有趣,生活中的好帮手!
600字范文 > Effective C++ 规则39:明智而谨慎的使用private继承

Effective C++ 规则39:明智而谨慎的使用private继承

时间:2020-03-20 19:26:07

相关推荐

Effective C++ 规则39:明智而谨慎的使用private继承

class Person { //... };class Student : private Person { //... };void eat(const Person& p);void study(const Student& s);void main(){Person p;Student s;eat(p);eat(s);}

这个时候,eat(s)就会报错,提示不允许对不可访问的基类Person进行转换。所以,可以看出,如果采用 private 继承:

1)编译器不会自动将一个 derived class 转换为一个 base class;

2)原来在 base class 中的所有 protected or public 成员,被 private 继承到 derived class 后,在derived class 中都会变成 private 属性;

private 继承意味着 is-implemented-in-terms-of如果 class D 以 private 方式继承了 class B,你的用意应该是希望使用 class B 中已经实现了的某些功能,也就是说,D 对象是根据 B 对象实现而来的,除此之外,没有其他意图。

前面一个条款说到复合(composition)也有 is-implemented-in-terms-of 的含义。那就是是选用 composition 还是 private 继承呢?答案是尽量使用 compostion,必要时才使用 private 继承。必要的情况有两种:

1)当有 protected 成员或者 virtual 函数牵扯进来时

假设现在有一个Widget类,我们需要让这个类记录每个成员函数的被调用次数,然后在运行期间周期性的审查记录的信息。为此,我们需要设定一个计时器。而且我们发现在现有的库函数中,Timer class可以满足需求:

class Timer{public:explicit Timer(int tickFrequency);virtual void onTick() const;//...};

其中的 virtual onTick() 函数就定义的时钟每滴答一次,就需要执行的动作,非常符合我们的需求。此时,为了让 Widget 类重写 Timer 内的 virtual 函数,Widget 必须继承 Timer。public 继承在此时并不恰当,因为 Widget is not a Timer。采用 public 继承,会让 Widget 继承 Timer 的不必要的接口,容易造成客户不正确使用 Widget 接口。所以此时,我们必须以 private 形式继承 Timer:

class Widget : private Timer{private:virtual void onTick() const;};

通过 private 继承,既能够重新定义virtual onTIck()函数,又能够使得原有的Timerclass 中的 public 接口隐藏起来,不对客户开放,保持了接口的不变性。

上面的设计已经可以较好的达到功能需求,但是别忘了,我们说过,如果 private 继承非必要,尽量采用 composition 的方式实现。对于上面的例子,也可以采用 composition 方式实现:

class Widget{private:class WidgetTimer : public Timer{public:virtual void onTick() const;//...};WidgetTimer timer;//...};

这种设计虽然复杂一些,但是可以有两个理由让你选择 composition 方式:

如果你想设计 Widget ,使得它拥有 derived classes,但是你同时又想阻止 derived class 重新定义 onTick。如果 Widget 继承了 Timer,即使是 privete 继承,上面的想法也不能够实现。但是如果 WidgetTimer 是 Widget 内部的一个 private 成员并继承了 Timer,derived Widget class 将无法使用 WidgetTimer,当然也就无法继承或重新定义它的 virtual 函数。降低了编译依存性(见这篇文章)。如果 Widget 继承了 Timer,那么必须 #include "Timer.h",那么 Widget 所在文件和 Timer.h 文件就存在编译依赖性。但如果采用 composition 方式,WidgetTimer 可以移出到 Widget 之外而 Widget 内部含有一个指向 WidgetTimer 的指针,Widget 可以只带一个 WidgetTimer 的声明就好了,不需要再 #include 任何与 Timer 有关的东西。

2)面对大小为零的独立对象时

如果现在有一个不含有数据成员的 empty class 和一个含有它的其他类:

class Empty { };class HoldsAnInt {private:int x;Empty e;};

你会发现,sizeof(HoldsAnInt) > sizeof(int),一个不含有数据成员的类居然还是需要内存;这是因为对于大多数编译器,在面对大小为 0 的独立(非附属)对象时,C++ 官方要求在背后安插一个 char 类型数据到空对象中。所以你会发现,sizeof(Empty) = 1。同时呢,由于对齐要求,HoldsAnInt 类还会加上一些 padding,导致最后 sizeof(HoldsAnInt ) 可能就等于 8 了。

但是,如果你继承一个 empty class,上面所说的就不成立了:

class HoldsAnInt : private Empty{private:int x;};

这个时候,几乎就可以确定sizeof(HoldsAnInt) == sizeof(int),这就是所说的 emty base optimization。如果你比较在意空间,这种 private 继承方式可能就是一种更好的选择。

本内容不代表本网观点和政治立场,如有侵犯你的权益请联系我们处理。
网友评论
网友评论仅供其表达个人看法,并不表明网站立场。