在《C++箴言:确保公开继承模拟“is-a”》一文中论述了 C++ 将 public inheritance(公有继承)视为一个 is-a 关系。当给定一个 hierarchy(继承体系),其中有一个 class Student 从一个 class Person 公有继承,当为一个函数调用的成功而有必要时,需要将 Students 隐式转型为 Persons,它通过向编译器展示来做到这一点。用 private inheritance(私有继承)代替 public inheritance(公有继承)把这个例子的一部分重做一下是值得的: *(G&B\
r}
Lb3`'
class Person { ... }; # :#M{1I
class Student: private Person { ... }; // inheritance is now private \PbvN\L
bpW!iY/q3
void eat(const Person& p); // anyone can eat ocF>LR%P
RvyuGU
void study(const Student& s); // only students study %zzYleJ!]
MTb,Kmw<(
Person p; // p is a Person EUy(T1Cl&&
Student s; // s is a Student /v|Onq1Y4
:?=Q39O9
eat(p); // fine, p is a Person nL+p~Hi
N5?IpE
eat(s); // error! a Student isn't a Person U&P{?>{u
*/w7?QOv
很明显,private inheritance(私有继承)不意味着 is-a。那么它意味着什么呢? *YtB )6j
56DoO'
“喂!”你说:“在我们得到它的含义之前,我们先看看它的行为。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(公有)的。 _({@B`N}
(WRMaI72(
行为不过如此。这就给我们带来了含义。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(接口)应该被忽略。 vT c7an6fy
+@ MPQv
如果 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(私有继承)倾斜。我们稍后再来操心这种极端情况。 D`pQ7
:g2
}C
毕竟,它只是一种极端情况。 假设我们工作在一个包含 Widgets 的应用程序上,而且我们认为我们需要更好地理解 Widgets 是怎样被使用的。例如,我们不仅要知道 Widget member functions(成员函数)被调用的频度,还要知道 call ratios(调用率)随着时间的流逝如何变化。带有清晰的执行阶段的程序在不同的执行阶段可以有不同的行为侧重。例如,一个编译器在解析阶段对函数的使用与优化和代码生成阶段就有很大的不同。 ]78!!G[`
cJDd0(tD!
我们决定修改 Widget class 以持续跟踪每一个 member function(成员函数)被调用了多少次。在运行时,我们可以周期性地检查这一信息,与每一个 Widget 的这个值相伴的可能还有我们觉得有用的其它数据。为了进行这项工作,我们需要设立某种类型的 timer(计时器),以便在到达收集用法统计的时间时我们可以知道。 ~~nqU pK?v
JJ?I>S N!
尽可能复用已有代码,而不是写新的代码,我在我的工具包中翻箱倒柜,而且满意地找到下面这个 class(类): ?^u^im
2.-o@im0
class Timer ?mx\eX{
{ -\#lF?fzb
public: (}smW_`5
explicit Timer(int tickFrequency); [Atc "X$
virtual void onTick() const; // automatically called for each tick Fi2xr<7"
... sN~ \+_
}; $wV1*$1NM
>2b`\Q*<
这正是我们要找的:一个我们能够根据我们的需要设定 tick 频率的 Timer object,而在每次 tick 时,它调用一个 virtual function(虚拟函数)。我们可以重定义这个 virtual function(虚拟函数)以便让它检查 Widget 所在的当前状态。很完美! rp's
为了给 Widget 重定义 Timer 中的一个 virtual function(虚拟函数),Widget 必须从 Timer 继承。但是 public inheritance(公有继承)在这种情况下不合适。Widget is-a(是一个)Timer 不成立。Widget 的客户不应该能够在一个 Widget 上调用 onTick,因为在概念上那不是的 Widget 的 interface(接口)的一部分。允许这样的函数调用将使客户更容易误用 Widget 的 interface(接口),这是一个对《使接口易于正确使用难错误使用》中的关于“使接口易于正确使用,而难以错误使用”的建议的明显违背。public inheritance(公有继承)在这里不是正确的选项。 m\ S\3n
JoZ(_Jh%m
因此我们就 inherit privately(秘密地继承): *fnvZw?
$dQIs:
class Widget: private Timer { mR%FqaN_
private: E{y1S\7K
virtual void onTick() const; // look at Widget usage data, etc. <*(^{a.O
... :,S98z#
}; z.oU4c
.[:VSM7T
通过 private inheritance(私有继承)的能力,Timer 的 public(公有)onTick 函数在 Widget 中变成 private(私有)的,而且在我们重新声明它的时候,也把它保留在那里。重复一次,将 onTick 放入 public interface(公有接口)将误导客户认为他们可以调用它,而这违背了我在《使接口易于正确使用难错误使用》。 8{0k0 &x
:Q_3hK
这是一个很好的设计,但值得注意的是,private inheritance(私有继承)并不是绝对必要的。如果我们决定用 composition(复合)来代替,也是可以的。我们仅需要在我们从 Timer 公有继承来的 Widget 内声明一个 private nested class(私有嵌套类),在那里重定义 onTick,并在 Widget 中放置一个那个类型的 object(对象)。以下就是这个方法的概要: %S@L|t
M`7y>Ud
class Widget { bgF^(T35
private: BRS#Fl:
class WidgetTimer: public Timer { epg#HNP7^Y
public: }Fa%%}
virtual void onTick() const; 0uvzxmN
... V'DA[{\*
}; ?o*I9[Z)
WidgetTimer timer; uw AwWgl
... G[,Q95`w?<
}; X~oK[Nf'9
ik.A1j9oN
这个设计比只用了 private inheritance(私有继承)的那一个更复杂,因为它包括 (public) inheritance((公有)继承)和 composition(复合)两者,以及一个新 class (WidgetTimer) 的引入。老实说,我出示它主要是为了提醒你有多于一条的道路通向一个设计问题,而且它也可以锻炼你自己你自己考虑多种方法(参见《C++箴言:最小化文件之间的编译依赖》)。然而,我可以想到为什么你可能更愿意用 public inheritance(公有继承)加 composition(复合)而不用 private inheritance(私有继承)的两个原因。 vLT0ETHg6
ZnW@YC#9
首先,你可能要做出允许 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++ 中的到类似行为的想法。 y_p.Gzy(^}
IiJZ5'{
第二,你可能需要最小化 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++箴言:最小化文件之间的编译依赖》)。 #Sh <