Private继承和public继承是软件生产中两个不同层面上的东西。Public侧重的是业务逻辑,它是从用户角度来想问题的。而private则纯粹是从实现角度来讲的,它意味着is-implemented-in-terms-of,即根据某物实现出什么的意思。
首先看一个例子:
可以看出通过子类对象无法调用父类的公有接口,而child和base是通过private方式继承的。这说明通过private方式继承的子类对象并不是一个父类对象,即不是IS-A的关系。
在面向对象程序设计的过程通常是模块化编程,每个模块是按照功能进行划分的,模块之外的通常都是用户,用户可调用的就是这个模块提供的接口,它们通常是public成员函数。而private继承体现了模块内部纯粹的实现细节和代码重用,因此private继承主要用在应用已经存在的具体实现实现出新的实现。
那这里private和复合都是is-implemented-in-terms-of关系,用哪个好呢?在这里作者极力推荐使用复合,不到万不得已不使用private继承,这倒不是说private继承不好,只是想表达private继承没啥必要,但是为啥没必要作者并未提及,作者只是说实现途径不只有private而已,这不禁让我感觉作者是在炫技或者纯粹是别出心裁思维的一种表述。
那么这个万不得已的时候是什么时候?作者说当涉及到protected成员、virtual成员函数、空间被限制得特别小特别严格的时候。
作者举了一个例子用来说明一种方案来代替private继承,我在这里就不再赘述这个例子是啥了,我只说说原理就OK了。因为当父类中含有virtual函数的时候,由于virtual函数的特性使之可在子类中被重新定义,因此不适宜使用public继承,不使用public继承,那自然就不满足IS-A的关系啦。
在这里使用private继承是一种好的选择,因为它有效地隐藏实现的细节,并且提供一个不具二义性的接口给用户。
它的一个替代实现方案是最靠近的是一个功能类A,它需要用到类B的实现,而这个类B又具备类C的所有特性。那现在的设计就是类A中包含了private的类B,并且类B以public方式继承了类C。然后在类A中以private方式声明一个类B的对象。这样所有涉及到virtual函数的重定义方面的事情都集中到B中进行处理和A一点关系都没有。这也是实现数据封装的一种方式,以后即便类A拥有自己的子类D,D也不会涉及到处理virtual函数的事。这样做也可以降低编译相依性,因为如果A直接继承C势必会导致加载C所在头文件,而如果按照上述方案A只需要B的一个声明。作者提到的这个方案还是个重要的解耦方案呢。
为什么说private继承适用于那些父类中有protected和virtual函数的场合呢?那是因为在private继承下继承的protected成员到子类中就是子类的private成员,父类的private成员还是父类的private成员跟子类一点关系都没有,而父类的public成员是面向用户的,它不需要子类去继承。所以你可以看出但凡是需要private继承的父类中最多只含有protected和private成员。而父类中如果含有virtual函数,那么它存在的目的就是要你去重定义,或者使用默认的实现,它不是public接口,还是实现的层面,而子类实现出来的东西就不是父类中的原版了,也就是说这不是IS-A的关系了,自然就不是public继承。那就只可能是private和protected继承了。而在这里我大胆地猜想protected继承应该是适用于那些连续继承实现的场合吧。因为如果是private继承,父类中的非private成员都会变成子类的private继承,而子类的private成员无论通过什么继承方式都无法再被其他类继承了。所以我觉得private继承是protected继承的终结。
那么对空间限制非常严格非常小的场合又是什么呢?这涉及到空类的继承与复合的区别。现在假设类A是空类,里面啥都没有,类B是正常类,那么有B复合A所占用的空间比B继承A所占用的空间大。具体来讲B中的各成员的存储的时候要求对齐存储,这一点我在《C与指针》有关结构体各成员在内存中的存储情况有过记录,类中各成员的存储情况也是类似的。而且一般来讲一个空类的对象所占的空间也不是0,一般情况下是1byte,因为C++规定一个独立的对象所占用的空间必须非0。这就决定你在采用复合时那个复合类对象所占用的空间中空类成员占用不只1byte。而你如果继承空类,那么子类对象中空类成分占用0byte。这种技巧被称为EBO(empty base optimization,空白基类最优化),不过它一般用于单继承。
它的作用在一般情况下是体现不出来的,因为一般的存储空间已经足够大了。它的实用性真正体现在像什么嵌入式这种空间非常小的场合。还有这个EBO技术所说的空类,其实也并不是啥都没有,typedef、enum、static、非virtual函数还是可能有的。请看下图:
其实,只要类中不含有非static成员变量,它就被认为是空的,比如说我再往上述例子中添加一个非static成员变量再看。
最后作者总结道:
1、private继承强调的是实现,它比复合低。当子类需要访问父类中的protected成员或者父类中还有virtual函数时,你需要private继承。
2、private是和空间最优化设计。