在《C++箴言:确保公开继承模拟“is-a”》一文中论述了 C++ 将 public inheritance(公有继承)视为一个 is-a 关系。当给定一个 hierarchy(继承体系),其中有一个 class Student 从一个 class Person 公有继承,当为一个函数调用的成功而有必要时,需要将 Students 隐式转型为 Persons,它通过向编译器展示来做到这一点。用 private inheritance(私有继承)代替 public inheritance(公有继承)把这个例子的一部分重做一下是值得的: iCk34C7
9>@_};l
class Person { ... }; \/K>Iv'$
class Student: private Person { ... }; // inheritance is now private (U/[i.r5Cj
+}4vdi"
void eat(const Person& p); // anyone can eat :CHCVoh@95
`-ENKr]
void study(const Student& s); // only students study Mw'd<{
|hp_X>Uv'
Person p; // p is a Person !;CY
@=
Student s; // s is a Student Vzbl*Zmx
L/nz95
eat(p); // fine, p is a Person zCvR/
G=Ka{J
eat(s); // error! a Student isn't a Person y''? yr
vn}Vb+@R
很明显,private inheritance(私有继承)不意味着 is-a。那么它意味着什么呢? Zb2 B5(0
Vg[U4,
“喂!”你说:“在我们得到它的含义之前,我们先看看它的行为。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(公有)的。
]g?G0m
oF,8j1
行为不过如此。这就给我们带来了含义。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(接口)应该被忽略。 yJb;V#
i-E&Y*\^9H
如果 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(私有继承)倾斜。我们稍后再来操心这种极端情况。 &2?kD{
k_}ICKzw1
毕竟,它只是一种极端情况。 假设我们工作在一个包含 Widgets 的应用程序上,而且我们认为我们需要更好地理解 Widgets 是怎样被使用的。例如,我们不仅要知道 Widget member functions(成员函数)被调用的频度,还要知道 call ratios(调用率)随着时间的流逝如何变化。带有清晰的执行阶段的程序在不同的执行阶段可以有不同的行为侧重。例如,一个编译器在解析阶段对函数的使用与优化和代码生成阶段就有很大的不同。 _\o +9X!
fngZ0k!
我们决定修改 Widget class 以持续跟踪每一个 member function(成员函数)被调用了多少次。在运行时,我们可以周期性地检查这一信息,与每一个 Widget 的这个值相伴的可能还有我们觉得有用的其它数据。为了进行这项工作,我们需要设立某种类型的 timer(计时器),以便在到达收集用法统计的时间时我们可以知道。 6dq U4
H`@x5RjS
尽可能复用已有代码,而不是写新的代码,我在我的工具包中翻箱倒柜,而且满意地找到下面这个 class(类): >Ckb9A
M5) 6|T
class Timer ZTS*E,U%
{ X#eVw|
public: TR)'I
explicit Timer(int tickFrequency); nh9K(
virtual void onTick() const; // automatically called for each tick |Z2_1(
ku
... 3U:0 ,-j"
}; ?'_iqg3
wp*;F#: G
这正是我们要找的:一个我们能够根据我们的需要设定 tick 频率的 Timer object,而在每次 tick 时,它调用一个 virtual function(虚拟函数)。我们可以重定义这个 virtual function(虚拟函数)以便让它检查 Widget 所在的当前状态。很完美! T,?^J-h^
为了给 Widget 重定义 Timer 中的一个 virtual function(虚拟函数),Widget 必须从 Timer 继承。但是 public inheritance(公有继承)在这种情况下不合适。Widget is-a(是一个)Timer 不成立。Widget 的客户不应该能够在一个 Widget 上调用 onTick,因为在概念上那不是的 Widget 的 interface(接口)的一部分。允许这样的函数调用将使客户更容易误用 Widget 的 interface(接口),这是一个对《使接口易于正确使用难错误使用》中的关于“使接口易于正确使用,而难以错误使用”的建议的明显违背。public inheritance(公有继承)在这里不是正确的选项。 Q_uv.\*z_
C t SAo\F
因此我们就 inherit privately(秘密地继承): ?on3z
@&M$`b
^
class Widget: private Timer { iwmXgsRa9}
private: TD3R/NP
virtual void onTick() const; // look at Widget usage data, etc. <<;j=Yy({`
... `C!Pe84(
}; t5e(9Yhj
}){hQt7
通过 private inheritance(私有继承)的能力,Timer 的 public(公有)onTick 函数在 Widget 中变成 private(私有)的,而且在我们重新声明它的时候,也把它保留在那里。重复一次,将 onTick 放入 public interface(公有接口)将误导客户认为他们可以调用它,而这违背了我在《使接口易于正确使用难错误使用》。 t<: XY
@[JQCQ#r
这是一个很好的设计,但值得注意的是,private inheritance(私有继承)并不是绝对必要的。如果我们决定用 composition(复合)来代替,也是可以的。我们仅需要在我们从 Timer 公有继承来的 Widget 内声明一个 private nested class(私有嵌套类),在那里重定义 onTick,并在 Widget 中放置一个那个类型的 object(对象)。以下就是这个方法的概要: )O7 Mfr
+JQN=nTA
class Widget { d0N7aacY
private: eN jC.w9
class WidgetTimer: public Timer { iWtWT1n8n
public: 2'@0|k,yC
virtual void onTick() const; fk",YtS*
... 2+?M(=4
}; OK2wxf
WidgetTimer timer; .eeM&