最近在看设计模式的时候,看到了私有继承,发现对这个概念挺模糊的,于是一顿搜索。发现一个不错的解释。红字我做的个人笔记,这是要点。

原文出处:http://tech.163.com/05/1124/14/23B2S17F0009159Q.html

在《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(公有)的。

行为不过如此。这就给我们带来了含义。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(私有继承)倾斜。我们稍后再来操心这种极端情况。(CHYGO:注意了,说到哪里使用了)

毕竟,它只是一种极端情况。 假设我们工作在一个包含 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...};

  这正是我们要找的:一个我们能够根据我们的需要设定 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(公有继承)在这里不是正确的选项。

因此我们就 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(私有继承)的两个原因。

  

首先,你可能要做出允许 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(复合)。

这个极端情况确实非常尖锐:它仅仅适用于你处理一个其中没有数据的 class(类)的时候。这样的 classes(类)没有 non-static data member(非静态数据成员);没有 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 memoryclass HoldsAnInt { // should need only space for an intprivate: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 的位置。(在我测试过的所有编译器上,这毫无例外地发生了。)

但是也许你已经注意到我小心翼翼地说 "freestanding" objects(“独立”对象)必然不会有 zero size。这个约束不适用于 base class parts ofderived 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(复合)更易于理解,所以  你应该尽你所能使用它(!!注意这句话!!尽量使用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(对象大小)的库开发者来说可能是很重要的。

转载于:https://www.cnblogs.com/CHYGO/articles/1901770.html

Private Inheritance(what, where)相关推荐

  1. Effective C++条款39:明智而审慎地使用private继承(Use private inheritance judiciously)

    Effective C++条款39:明智而审慎地使用private继承(Use private inheritance judiciously) 条款39:明智而审慎地使用private继承 1.pr ...

  2. Public Private Protect Inheritance and access specifiers

    In the previous lessons on inheritance, we've been making all of our data members public in order to ...

  3. 条款39:明智而审慎的使用private继承

    Use private inheritance judiciously. 如果classes之间的继承关系是private,编译器不会自动将一个derived class对象转换为一个base cla ...

  4. public protect private继承

    引自原来在CGD讨论的几点总结: 1. public继承被称为类型继承(type inheritance).往往反映is-a关系. 2. protected继承基类的所有公有成员都成为派生类的prot ...

  5. C++ inheritance examples

    1.C++继承经典例子 1 #include <iostream> 2 using namespace std; 3 class Base 4 { 5 private: 6 int b_n ...

  6. C++进阶_Effective_C++第三版(六) 继承与面向对象设计 Inheritance and Object-Oriented Design

    继承与面向对象设计 Inheritance and Object-Oriented Design 面向对象编程已经风靡编程界,关于继承.派生.virtual函数等等需要深入了解. 32.确定你的pub ...

  7. [翻译] Effective C++, 3rd Edition, Item 32: 确保 public inheritance 模拟 is-a(上)

    Item 32: 确保 public inheritance 模拟 "is-a" 作者:Scott Meyers 译者:fatalerror99 (iTePub's Nirvana ...

  8. C++中static_cast/const_cast/dynamic_cast/reinterpret_cast的区别和使用

    C风格的强制转换较简单,如将float a转换为int b,则可以这样:b = (int)a,或者b=int(a). C++类型转换分为隐式类型转换和显示类型转换. 隐式类型转换又称为标准转换,包括以 ...

  9. cocos2d-x C++ 原始工程引擎运行机制解析

    新建一个工程,相信感兴趣的同学都想知道cocos引擎都是如何运行的 想知道是如何运行的,看懂四个文件即可 话不多说,上代码: 1.首先解释 AppDelegate.h 1 #ifndef _APP_D ...

最新文章

  1. 针对七牛含有特殊字符的文件名,对特殊字符编码处理
  2. 1.matlab 中的axis tight,legend
  3. matlab定子磁链观测器,一种基于二阶广义积分器的永磁同步电机定子磁链观测方法...
  4. python是一种动态语言、这意味着_【python编程的优点是什么?难怪选择python的人越来越多了】- 环球网校...
  5. 快速安装第三方库的指令——解决第三方库安装超时问题
  6. 如何把自己的网站部署在网上_Terminal:如何在IPFS上部署Wordpress网站
  7. IO多路复用之select全面总结(必看篇)
  8. Quartz CronTrigger最完整配置说明
  9. Linux磁盘管理:LVM逻辑卷管理
  10. php抓取视频教程,PHP抓取、分析国内视频网站的视频信息工具类_PHP
  11. 软件测试达内视频笔记(二)
  12. [eclipse error]'Android Dependencies' which does not allow modifications to source attachments on it
  13. MAC快捷键---8
  14. 侠客群控引擎二次开发SDK可用方法大全(持续更新)
  15. 使用Vue前端框架实现知乎日报app
  16. 顶级黑客分享的30个极简Python代码,拿走就能用!
  17. 常见java空指针异常
  18. 初识马尔可夫和马尔可夫链
  19. 数字IC秋招---笔试记录
  20. labview控制 西门子S7-1200 1214 dcdcdcplc 程序

热门文章

  1. 计算机教学难点重点,浅述如何解决小学信息技术教学中的重点、难点
  2. cross join 一张表没有值关联不出来数据_你是否还在对left join、right join和join有困扰呢?...
  3. 编译linux内核适用的编译器,编译Linux内核时,CC,LD和CC [M]输出的代码是什么?...
  4. C语言(CED)递归实现汉诺塔问题
  5. google protobuf_protobuf 指南
  6. android 首页广告显示不出来的,android – Admob插页式广告(全屏)不会显示
  7. java移动端接口测试_使用java如何进行接口测试
  8. nova8pro能升级鸿蒙吗,华为将有48款产品可以升级到鸿蒙 2.0系统
  9. 数据卡片_E015 如何批量汇总工作簿数据,形成独立工作簿信息卡片
  10. 计算机入门 姚班,清华“姚班”:学霸中的尖子生,大佬毕业后都去哪了?