C++箴言:审慎利用私有担任
当前位置:以往代写 > C/C++ 教程 >C++箴言:审慎利用私有担任
2019-06-13

C++箴言:审慎利用私有担任

C++箴言:审慎利用私有担任

副标题#e#

在《C++箴言:确保果真担任模仿“is-a”》一文中阐述了 C++ 将 public inheritance(公有担任)视为一个 is-a 干系。当给定一个 hierarchy(担任体系),个中有一个 class Student 从一个 class Person 公有担任,当为一个函数挪用的乐成而有须要时,需要将 Students 隐式转型为 Persons,它通过向编译器展示来做到这一点。用 private inheritance(私有担任)取代 public inheritance(公有担任)把这个例子的一部门重做一下是值得的:

class Person { ... };
class Student: private Person { ... }; // inheritance is now private
void eat(const Person& p); // anyone can eat
void study(const Student& s); // only students study
Person p; // p is a Person
Student s; // s is a Student
eat(p); // fine, p is a Person
eat(s); // error! a Student isn't a Person

很明明,private inheritance(私有担任)不料味着 is-a。那么它意味着什么呢?

“喂!”你说:“在我们获得它的寄义之前,我们先看看它的行为。private inheritance(私有担任)有奈何的行为呢?”好吧,支配 private inheritance(私有担任)的第一个法则你只能从行动中看到:与 public inheritance(公有担任)比较,假如 classes(类)之间的 inheritance relationship(担任干系)是 private(私有)的,编译器凡是不会将一个 derived class object(派生类工具)(诸如 Student)转型为一个 base class object(基类工具)(诸如 Person)。这就是为什么为 object(工具)s 挪用 eat 会失败。第二个法则是从一个 private base class(私有基类)担任的 members(成员)会成为 derived class(派生类)的 private members(私有成员),纵然它们在 base class(基类)中是 protected(掩护)的或 public(公有)的。


#p#副标题#e#

行为不外如此。这就给我们带来了寄义。private inheritance(私有担任)意味着 is-implemented-in-terms-of(是按照……实现的)。假如你使 class(类)D 从 class(类)B 私有担任,你这样做是因为你对付操作在 class(类)B 中才可用的某些特性感乐趣,而不是因为在 types(范例)B 和 types(范例)D 的 objects(工具)之间有什么观念上的干系。同样地,private inheritance(私有担任)纯粹是一种实现技能。(这也就是为什么你从一个 private base class(私有基类)担任的每一件对象都在你的 class(类)中酿成 private(私有)的原因:它全部都是实现的细节。)操作《接口担任和实现担任》中提出的条款,private inheritance(私有担任)意味着只有 implementation(实现)应该被担任;interface(接口)应该被忽略。

假如 D 从 B 私有担任,它就意味着 D objects are implemented in terms of B objects(D 工具是按照 B 工具实现的),没有更多了。private inheritance(私有担任)在 software design(软件设计)期间没有任何意义,只在 software implementation(软件实现)期间才有。 private inheritance(私有担任)意味着 is-implemented-in-terms-of(是按照……实现的)的事实有一点杂乱,正如《通过composition模仿“has-a”》一文中所指出的 composition(复合)也有同样的寄义。你怎么预先在它们之间做出选择呢?谜底很简朴:只要你能就用 composition(复合),只有在绝对须要的时候才用 private inheritance(私有担任)。什么时候是绝对须要呢?主要是当 protected members(掩护成员)和/或 virtual functions(虚拟函数)掺和进来的时候,别的尚有一种与空间相关的极度环境会使天平向 private inheritance(私有担任)倾斜。我们稍后再来劳神这种极度环境。

#p#副标题#e#

究竟,它只是一种极度环境。 假设我们事情在一个包括 Widgets 的应用措施上,并且我们认为我们需要更好地领略 Widgets 是奈何被利用的。譬喻,我们不只要知道 Widget member functions(成员函数)被挪用的频度,还要知道 call ratios(挪用率)跟着时间的流逝如何变革。带有清晰的执行阶段的措施在差异的执行阶段可以有差异的行为偏重。譬喻,一个编译器在理会阶段对函数的利用与优化和代码生成阶段就有很大的差异。

我们抉择修改 Widget class 以一连跟踪每一个 member function(成员函数)被挪用了几多次。在运行时,我们可以周期性地查抄这一信息,与每一个 Widget 的这个值相伴的大概尚有我们以为有用的其它数据。为了举办这项事情,我们需要设立某种范例的 timer(计时器),以便在达到收集用法统计的时间时我们可以知道。

尽大概复用已有代码,而不是写新的代码,我在我的东西包中翻箱倒柜,并且满足地找到下面这个 class(类):

class Timer {
public:
explicit Timer(int tickFrequency);
virtual void onTick() const; // automatically called for each tick
...
};

#p#分页标题#e#

这正是我们要找的:一个我们可以或许按照我们的需要设定 tick 频率的 Timer object,而在每次 tick 时,它挪用一个 virtual function(虚拟函数)。我们可以重界说这个 virtual function(虚拟函数)以便让它查抄 Widget 地址的当前状态。很完美!

为了给 Widget 重界说 Timer 中的一个 virtual function(虚拟函数),Widget 必需从 Timer 担任。可是 public inheritance(公有担任)在这种环境下不符合。Widget is-a(是一个)Timer 不创立。Widget 的客户不该该可以或许在一个 Widget 上挪用 onTick,因为在观念上那不是的 Widget 的 interface(接口)的一部门。答允这样的函数挪用将使客户更容易误用 Widget 的 interface(接口),这是一个对《使接口易于正确利用难错误利用》中的关于“使接口易于正确利用,而难以错误利用”的发起的明明违背。public inheritance(公有担任)在这里不是正确的选项。

#p#副标题#e#

因此我们就 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(公有接口)将误导客户认为他们可以挪用它,而这违背了我在《使接口易于正确利用难错误利用》。

这是一个很好的设计,但值得留意的是,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) 的引入。诚恳说,我出示它主要是为了提醒你有多于一条的阶梯通向一个设计问题,并且它也可以熬炼你本身你本身思量多种要领(拜见《C++箴言:最小化文件之间的编译依赖》)。然而,我可以想到为什么你大概更愿意用 public inheritance(公有担任)加 composition(复合)而不消 private inheritance(私有担任)的两个原因。

#p#副标题#e#

首先,你大概要做出答允 Widget 有 derived classes(派生类)的设计,可是你还大概要克制 derived classes(派生类)重界说 onTick。假如 Widget 从 Timer 担任,那是不行能的,纵然 inheritance(担任)是 private(私有)的也不可。(回想《C++箴言:思量可选的虚拟函数的替代要领》derived classes(派生类)可以重界说 virtual functions(虚拟函数),纵然挪用它们是不被答允的。)可是假如 WidgetTimer 在 Widget 中是 private(私有)的并且是从 Timer 担任的,Widget 的 derived classes(派生类)就不能会见 WidgetTimer,因此就不能从它担任或重界说它的 virtual functions(虚拟函数)。假如你曾在 Java 或 C# 中编程而且错过了克制 derived classes(派生类)重界说 virtual functions(虚拟函数)的本领(也就是,Java 的 final methods(要领)和 C# 的 sealed),此刻你有了一个在 C++ 中的到雷同行为的想法。

第二,你大概需要最小化 Widget 的 compilation dependencies(编译依赖)。假如 Widget 从 Timer 担任,在 Widget 被编译的时候 Timer 的 definition(界说)必需是可用的,所以界说 Widget 的文件大概不得不 #include Timer.h。另一方面,假如 WidgetTimer 移出 Widget 而 Widget 只包括一个指向一个 WidgetTimer 的 pointer(指针),Widget 就可以只需要 WidgetTimer class(类)的一个简朴的 declaration(声明);为了利用 Timer 它不需要 #include 任何对象。对付大型系统,这样的断绝大概很是重要(关于 minimizing compilation dependencies(最小化编译依赖)的细节,拜见《C++箴言:最小化文件之间的编译依赖》)。

我早些时候谈及 private inheritance(私有担任)主要用武之地是当一个将要成为 derived class(派生类)的类需要会见将要成为 base class(基类)的类的 protected parts(掩护构件),可能但愿重界说一个或多个它的 virtual functions(虚拟函数),可是 classes(类)之间的观念上的干系却是 is-implemented-in-terms-of,而不是 is-a。然而,我也说过有一种涉及 space optimization(空间最优化)的极度环境大概会使你倾向于 private inheritance(私有担任),而不是 composition(复合)。

#p#副标题#e#
#p#分页标题#e#

这个极度环境确实很是厉害:它仅仅合用于你处理惩罚一个个中没有数据的 class(类)的时候。这样的 classes(类)没有 non-static data members(非静态数据成员);没有 virtual functions(虚函数)(因为存在这样的函数会在每一个 object(工具)中增加一个 vptr ——拜见《C++箴言:多态基类中将析构函数声明为虚拟》);也没有 virtual base classes(虚拟基类)(因为这样的 base classes(基类)也会引起 size overhead(巨细本钱))。在理论上,这样的 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(对齐需求)大概促使编译器向雷同 HoldsAnInt 的 classes(类)中增加填充物,所以,很大概 HoldsAnInt objects 获得的不只仅是一个 char 的巨细,实际上它们大概会扩张到足以占据第二个 int 的位置。(在我测试过的所有编译器上,这毫无破例地产生了。)

#p#副标题#e#

可是也许你已经留意到我小心翼翼地说 "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_function 和 binary_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

#p#分页标题#e#

·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(工具巨细)的库开拓者来说大概是很重要的。

    关键字:

在线提交作业