[翻译] Effective C++, 3rd Edition, Item 39: 谨慎使用 private inheritance(私有继承)(下)

(点击此处,接上篇)

因此我们就 inherit privately(秘密地继承):

class Widget: private Timer {
private:
  virtual void onTick() const;           // look at Widget usage data, etc.
  ...
};

通过 private inheritance(私有继承)的能力,Timer 的 public(公有)onTick 函数在 Widget 中变成 private(私有)的,而且在我们重新声明它的时候,也把它保留在那里。重复一次,将 onTick 放入 public interface(公有接口)将误导客户认为他们可以调用它,而这违背了 Item 18

这是一个很好的设计,但值得一提的是,private inheritance(私有继承)并不是绝对必要的。如果我们决定用 composition(复合)来代替,也是可以的。我们仅需要在我们从 Timer 公有继承来的 Widget 内声明一个 private nested class(私有嵌套类),在那里重定义 onTick,并在 Widget 中放置一个那个类型的 object(对象)。以下就是这个方法的概要:

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

 

这个设计比只用了 private inheritance(私有继承)的那一个更复杂,因为它包括 (public) inheritance((公有)继承)和 composition(复合)两者,以及一个新 class (WidgetTimer) 的引入。老实说,我出示它主要是为了提醒你有多于一条的道路通向一个设计问题,而且它对于考虑多种方法的自我训练也有相当的价值(参见 Item 35)。然而,我可以想到为什么你可能更愿意用 public inheritance(公有继承)加 composition(复合)而不用 private inheritance(私有继承)的两个原因。

首先,你可能要做出允许 Widget 有 derived classes(派生类)的设计,但是你还可能要禁止 derived classes(派生类)重定义 onTick。如果 WidgetTimer 继承,那是不可能的,即使 inheritance(继承)是 private(私有)的也不行。(回忆 Item 35 derived classes(派生类)可以重定义 virtual functions(虚拟函数),即使调用它们是不被允许的。)但是如果 WidgetTimerWidget 中是 private(私有)的而且是从 Timer 继承的,Widget 的 derived classes(派生类)就不能访问 WidgetTimer,因此就不能从它继承或重定义它的 virtual functions(虚拟函数)。如果你曾在 Java 或 C# 中编程并且错过了禁止 derived classes(派生类)重定义 virtual functions(虚拟函数)的能力(也就是,Java 的 final methods(方法)和 C# 的 sealed),现在你有了一个在 C++ 中的到类似行为的想法。

第二,你可能需要最小化 Widget 的 compilation dependencies(编译依赖)。如果 WidgetTimer 继承,在 Widget 被编译的时候 Timer 的 definition(定义)必须是可用的,所以定义 Widget 的文件可能不得不 #include Timer.h。另一方面,如果 WidgetTimer 移出 WidgetWidget 只包含一个指向一个 WidgetTimer 的 pointer(指针),Widget 就可以只需要 WidgetTimer class(类)的一个简单的 declaration(声明);为了使用 Timer 它不需要 #include 任何东西。对于大型系统,这样的隔离可能非常重要(关于 minimizing compilation dependencies(最小化编译依赖)的细节,参见 Item 31)。

我早些时候谈及 private inheritance(私有继承)主要用武之地是当一个将要成为 derived class(派生类)的类需要访问将要成为 base class(基类)的类的 protected parts(保护构件),或者希望重定义一个或多个它的 virtual functions(虚拟函数),但是 classes(类)之间的概念上的关系却是 is-implemented-in-terms-of,而不是 is-a。然而,我也说过有一种涉及 space optimization(空间最优化)的极端情况可能会使你倾向于 private inheritance(私有继承),而不是 composition(复合)。

这个极端情况确实非常尖锐:它仅仅适用于你处理一个其中没有数据的 class(类)的时候。这样的 classes(类)没有 non-static data members(非静态数据成员);没有 virtual functions(虚函数)(因为存在这样的函数会在每一个 object(对象)中增加一个 vptr ——参见 Item 7);也没有 virtual base classes(虚拟基类)(因为这样的 base classes(基类)也会引起 size overhead(大小成本)——参见 Item 40)。在理论上,这样的 empty classes(空类)的 objects(对象)应该不占用空间,因为没有 per-object(逐对象)的数据需要存储。然而,由于 C++ 天生的技术上的原因,freestanding objects(独立对象)必须有 non-zero size(非零大小),所以如果你这样做,

class Empty {};                      // has no data, so objects should
                                     // use no memory
class HoldsAnInt {                   // should need only space for an int
private:
  int x;
  Empty e;                           // should require no memory
};

你将发现 sizeof(HoldsAnInt) > sizeof(int);一个 Empty data member(空数据成员)需要存储。对以大多数编译器,sizeof(Empty) 是 1,这是因为 C++ 法则反对 zero-size 的 freestanding objects(独立对象)一般是通过在 "empty" objects(“空”对象)中插入一个 char 完成的。然而,alignment requirements(对齐需求)(参见 Item 50)可能促使编译器向类似 HoldsAnInt 的 classes(类)中增加填充物,所以,很可能 HoldsAnInt objects 得到的不仅仅是一个 char 的大小,实际上它们可能会扩张到足以占据第二个 int 的位置。(在我测试过的所有编译器上,这毫无例外地发生了。)

但是也许你已经注意到我小心翼翼地说 "freestanding" objects(“独立”对象)必然不会有 zero size。这个约束不适用于 base class parts of derived class objects(派生类对象的基类构件),因为它们不是独立的。如果你用从 Empty 继承代替包含一个此类型的 object(对象),

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

你几乎总是会发现 sizeof(HoldsAnInt) == sizeof(int)。这个东西以 empty base optimization (EBO)(空基优化)闻名,而且它已经被我测试过的所有编译器实现。如果你是一个空间敏感的客户的库开发者,EBO 就值得了解。同样值得了解的是 EBO 通常只在 single inheritance(单继承)下才可行。支配 C++ object layout(C++ 对象布局)的规则通常意味着 EBO 不适用于拥有多于一个 base(基)的 derived classes(派生类)。

在实践中,"empty" classes(“空”类)并不真的为空。虽然他们绝对不会有 non-static data members(非静态数据成员),但它们经常会包含 typedefs,enums(枚举),static data members(静态数据成员),或 non-virtual functions(非虚拟函数)。STL 有很多包含有用的 members(成员)(通常是 typedefs)的专门的 empty classes(空类),包括 base classes(基类)unary_functionbinary_function,user-defined function objects(用户定义函数对象)通常从这些 classes(类)继承而来。感谢 EBO 的普遍实现,这样的继承很少增加 inheriting classes(继承来的类)的大小。

尽管如此,我们还是要回归基础。大多数 classes(类)不是空的,所以 EBO 很少会成为 private inheritance(私有继承)的一个合理的理由。此外,大多数 inheritance(继承)相当于 is-a,而这正是 public inheritance(公有继承)而非 private(私有)所做的事。composition(复合)和 private inheritance(私有继承)两者都意味着 is-implemented-in-terms-of(是根据……实现的),但是 composition(复合)更易于理解,所以你应该尽你所能使用它。

private inheritance(私有继承)更可能在以下情况中成为一种设计策略,当你要处理的两个 classes(类)不具有 is-a(是一个)的关系,而且其中的一个还需要访问另一个的 protected members(保护成员)或需要重定义一个或更多个它的 virtual functions(虚拟函数)。甚至在这种情况下,我们也看到 public inheritance 和 containment 的混合使用通常也能产生你想要的行为,虽然有更大的设计复杂度。谨慎使用 private inheritance(私有继承)意味着在使用它的时候,已经考虑过所有的可选方案,只有它才是你的软件中明确表示两个 classes(类)之间关系的最佳方法。

Things to Remember

  • private inheritance(私有继承)意味着 is-implemented-in-terms of(是根据……实现的)。它通常比 composition(复合)更低级,但当一个 derived class(派生类)需要访问 protected base class members(保护基类成员)或需要重定义 inherited virtual functions(继承来的虚拟函数)时它就是合理的。
  • 与 composition(复合)不同,private inheritance(私有继承)能使 empty base optimization(空基优化)有效。这对于致力于最小化 object sizes(对象大小)的库开发者来说可能是很重要的。
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 4
    评论
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值