private继承的意义在于“be implemented in turns of”,这个与上一条款中说的复合模型的第二层含义是相同的,这也意味着通常我们可以在这两种设计方法之间转换,但书上还是更提倡使用复合来进行类的设计。

private继承与public的继承是完全不同的,主要体现在两个地方:

其一,public继承在子类中保持父类的访问权限,即父类中是public的成员函数或成员变量,在子类中仍是public,对private或者protected的成员函数或成员变量亦是如此;但private继承则不是这样了,它破坏了父类中的访问权限标定,将之都转成private,这对子类本身并无影响(照常访问),但却影响了子类的子类,子类的子类将无法访问这些声明/定义在爷爷辈类的成员变量或成员函数。

其二,Liskov法则不再适用,也就是说“一切父类出现的地方都可以被子类所替代”的法则在private这里不成立,请看下面的例子(来源自书上):

 #include <iostream>
using namespace std; class Person
{}; class Student : private Person
{}; void eat(const Person& p){} int main()
{
Person p;
Student s;
eat(p); // OK
eat(s); // 编译报错:error C2243: “类型转换”: 从“Student *”到“const Person &”的转换存在,但无法访问
}

但如果令Student公有继承Person,则编译器不会报错。这正是Liskov的可替代原则在private继承中不适用的体现。

private继承使用的地方实在不多,除非有一些奇葩的设计需求,书上说了一个例子:

 class TypeDefine
{}; class SimpleClass
{
int a;
TypeDefine obj;
}; class SimpleDerivedClass : private TypeDefine
{
int a;
}; int main()
{
cout << sizeof(TypeDefine) << endl; // ?
cout << sizeof(SimpleClass) << endl; // ?
cout << sizeof(SimpleDerivedClass) << endl; // ?
}

大家可以想一下“?”处的输出是什么。第一个是空类,空类就像是空气一样,仅仅是名字里面包含了“空”字,看起来是“空”的,但其实不是这样子的,空气里面混合了氧、氮、二氮化碳等气体,还有各种微生物,而对于空类,编译器会为之生成四个默认的函数:默认构造函数,默认拷贝构造,默认析构函数,默认赋值运算符。读者就会问了,编译器生成了默认的函数不假,但函数是不占空间的,为什么空类的sizeof算出的值是1?原来类的每一个对象都需要一个独一无二的内存地址,所以编译器会在空类对象中插入一个1字节变量,正是这个1字节的变量,才能够区分空类的不同对象。非空类因为已经有了成员变量,所以编译器可以利用这些成员变量来进行内存地址的区分,从而标识类的不同对象,这个时候是不需要插入一个1字节的变量的。所以第一个问号处输出的是1。

第二个问号输出的是5吗?int四字节再加到空类对象的四字节?理论上是这样,但编译器还会做一种内存对齐的操作,使得类对象的大小会是处理字长的整数倍,一般是4字节的整数倍,所以最后的结果其实是8。

第三个问号呢?前面讲的那么多,好像都与private无关,这个问题终于与它有关了。运行下看看,结果是4。为什么用复合模型时输出的结果是8,但private继承时却是4呢?这其实是编译器做了空白基类优化(EBO),原本是要为空白类对象插入1字节的,但因为子类中已经有了对象了,这样理论上就可以凭借这个对象来进行同一个类不同对象间的识别了,所以这时候编译器就不再插入字节了。

这个结果就是用private继承的好处,是不是很奇葩呢~所以我说,在大部分情况下,都不会考虑private继承,因为它的含义be implemented in terms of 可以用复合来替换。

书上还提到了关于虚函数不想被子类的子类所覆写的问题,这时候不能用private限制虚函数,因为生成的虚指针是一直会被继承下去的,解决方法就是用复合,而且复合的类是一个临时类且复合对象标记为private,这样就只能限制在这个类本身去覆写了。具体的例子可以去看原书。

最后总结一下:

1. Private继承意味着is implemented in terms of,它通常比复合的级别低(即优先使用复合),但是当derived class需要访问protected base class的成员,或需要重新定义继承而来的virtual函数时,这么设计是合理的。

2. 与复合不同,private继承可以造成empty base最优化。这对致力于“对象尺寸最小化”的程序库开发者而言,可能很重要。

读书笔记_Effective_C++_条款三十九:明智而审慎地使用private继承的更多相关文章

  1. 读书笔记_Effective_C++_条款四十:明智而审慎地使用多重继承

    多重继承是一种比较复杂的继承关系,它意味着如果用户想要使用这个类,那么就要对它的父类也了如指掌,所以在项目中会带来可读性的问题,一般我们都会尽量选择用单继承去替代它. 使用多重继承过程容易碰到的问题就 ...

  2. 读书笔记_Effective_C++_条款三十:了解inline的里里外外

    学过基本程序课的同学都知道,inline是内联的关键字,它可以建议编译器将函数的每一个调用都用函数本体替换.这是一种以空间换时间的做法.把每一次调用都用本体替换,无疑会使代码膨胀,但可以节省函数调用的 ...

  3. 读书笔记_Effective_C++_条款二十九:为“异常安全”而努力是值得的

    还是举书上的例子: void PrettyMenu::changeBackground(std::istream& imgSrc) { lock(&mutex); delete bgI ...

  4. 读书笔记_Effective_C++_条款三十四:区分接口继承和实现继承

    这个条款书上内容说的篇幅比较多,但其实思想并不复杂.只要能理解三句话即可,第一句话是:纯虚函数只继承接口:第二句话是:虚函数既继承接口,也提供了一份默认实现:第三句话是:普通函数既继承接口,也强制继承 ...

  5. 读书笔记_Effective_C++_条款三十二:确定你的public继承继承塑模出is-a关系

    这一条款是说的是公有继承的逻辑,如果使用继承,而且继承是公有继承的话,一定要确保子类是一种父类(is-a关系).这种逻辑可能与生活中的常理不相符,比如企鹅是生蛋的,所有企鹅是鸟类的一种,直观来看,我们 ...

  6. 读书笔记_Effective_C++_条款四十九:了解new_handler的行为

    本章开始讨论内存分配的一些用法,C/C++内存分配采用new和delete.在new申请内存时,可能会遇到的一种情况就是,内存不够了,这时候会抛出out of memory的异常.有的时候,我们希望能 ...

  7. 读书笔记_Effective_C++_条款三十六:绝不重新定义继承而来的non-virtual函数

    这个条款的内容很简单,见下面的示例: class BaseClass { public: void NonVirtualFunction() { cout << "BaseCla ...

  8. 读书笔记_Effective_C++_条款三十八:通过复合塑模出has-a或者is-implemented-in-terms-of

    如果说public是一种is-a的关系的话,那么复合就是has-a的关系.直观来说,复合就是在一个类中采用其他类的对象作为自身的成员变量,可以举个例子,像下面这样: class Person { pr ...

  9. 读书笔记_Effective_C++_条款三十五:考虑virtual函数以外的其他选择

    举书上的例子,考虑一个virtual函数的应用实例: class GameCharacter { private: int BaseHealth; public: virtual int GetHea ...

随机推荐

  1. Python之容器类Collections

    容器类Collections 标签(空格分隔): Python进阶 defaultdict counter deque namedtuple defaultdict defaultdict的作用是可以 ...

  2. 在JAVA中记录日志的十个小建议

    JAVA日志管理既是一门科学,又是一门艺术.科学的部分是指了解写日志的工具以及其API,而选择日志的格式,消息的格式,日志记录的内容,哪种消息对应于哪一种日志级别,则完全是基于经验.从过去的实践证明, ...

  3. 安卓ios各版本及分辨率占比

    Google Play 安装统计数据 只有安卓的 https://developer.android.com/about/dashboards/index.html?hl=zh-cn 腾讯移动分析 安 ...

  4. UFLDL 教程学习笔记(四)

    课程地址:http://ufldl.stanford.edu/tutorial/supervised/FeatureExtractionUsingConvolution/ 在之前的练习中,图片比较小, ...

  5. HDU 4283 You Are the One(区间DP(最优出栈顺序))

    题目链接:http://acm.hdu.edu.cn/showproblem.php?pid=4283 题目大意:有一群屌丝,每个屌丝有个屌丝值,如果他第K个上场,屌丝值就为a[i]*(k-1),通过 ...

  6. LeetCode403. Frog Jump

    A frog is crossing a river. The river is divided into x units and at each unit there may or may not ...

  7. Java学习(正则表达式、Date类、DateFormat类、Calendar类)

    一.正则表达式 1.概念:英语:Regular Expression,在代码中常简写为regex.正则表达式,是一个字符串,使用单个字符串来描述.用来定义匹配规则,匹配一系列符合某个句法规则的字符串. ...

  8. ASP.NET WebAPI 01-Demo

    WebAPI作为构建RESTful的平台出来有段时间了,加上最近也在用,所以想把自己的心得记录下来.我就以一个简单的增删查改作为开篇. 准备 实体类(Figure)的定义. public class ...

  9. django视图函数中 应用装饰器

    from django.shortcuts import render, redirect, HttpResponse from .forms import LoginForm, Registrati ...

  10. Html5游戏开发攻略(免费的音乐面包篇)

    这一篇我们来尝尝免费的面包,至少目前是这样的. QQ音乐相信大家或多或少都使用过,里面的音乐资源非常多. 这个时候你可能就明白了,没错,我们要在游戏中使用QQ音乐的资源当背景音乐~~~~~哦耶~! 咳 ...