读书笔记:Effective C++ 炒冷饭 - Item 38&39 “有一个” & 小心使用私有继承
[原创文章欢迎转载,但请保留作者信息]
Justin 于 2010-02-23
当然ITEM38其实也没什么需要多记的,ITEM32中说到了“是一个”的模型,这里要回顾的是“有一个”的模型。(原文中有两种说法,has a和implement in terms of,然而个人窃以为意思其实差不多,在笔记里就不钻牛角尖了……)
在“是一个”模型不适用于构建一个类或是一种关系的时候,可以考虑“有一个”。大师给的例子应该是被举例举烂了的:一个人“有一个”地址,“有一个”电话号码,诸如此类。
与“是一个”不同,“有一个”模型不能用公有继承实现,而是以类成员的方式构造的,这个道理也很浅显:类与其成员之间的关系本来就是“有一个”。
到了第39条军规,讲的是和私有继承有关的事情。
公有继承中的子类对象是可以被转换为它的父类对象的(“是一个”的关系),而私有继承中这种转换是不成立的。
另外一点,私有继承中父类的所有公有和保护成员(public和protected)到了子类中,都变成了私有成员。
因为上面的特性,私有继承并不满足“是一个”模型的需要。更可怜的是,私有继承并不能代表一种设计思路(公有继承代表了“是一个”的模型设计),而仅仅是“有一个”模型的一种实现手段(私有继承过来的所有成员都是私有的,从这个角度来说它就只是“实现”)。
另一种手段大师在Item38中有提过,就是用类成员的方式来构造,名曰composition。
既然两者都能实现“有一个”模型,那么如何选择呢?能用composition就用composition,必需私有继承的时候方才私有继承。
比如我们有个AClass:
class
AClass{
public :
virtual void Interface_1( /* .. */ );
};
public :
virtual void Interface_1( /* .. */ );
};
以下为私有继承:
class
BClass :
private
AClass{
private :
virtual void Interface_1( /* .. */ );
// ..
};
private :
virtual void Interface_1( /* .. */ );
// ..
};
而下面的composition可以达到一样甚至更好的效果:
class
AnotherAClass:
public
AClass{
public :
virtual void Interface_1( /* .. */ );
// ..
};
class DClass{
private :
AnotherAClass * a;
// ..
};
public :
virtual void Interface_1( /* .. */ );
// ..
};
class DClass{
private :
AnotherAClass * a;
// ..
};
【以上代码纯粹是对大师例程的简陋抄袭,大师见谅……】
BClass和DClass都实现了“有一个”,但相比之下还是能分辨出长短:
- DClass中的AnotherAClass是私有成员,除了它自己没有人能够访问修改;而私有继承的BClass不能保证其“拥有”的AClass实现部分不会被第三者修改,即使是私有继承来的。(为什么这么说?看下去……)
BClass私有继承了AClass,相当于它“有了一个”AClass可以用,可以玩。AClass中的公有/保护成员都变成了BClass的人,但是在享受使用这些成员的同时,BClass还要承担提供这些成员给别人服务的义务。
ITEM35中曾经提到:虚拟函数机制和公有/私有/保护体制是没有任何关系的。因此在例子中的Interface_1有可能在以下的情况中被替代然后“调用”: - 一个CClass公有继承了BClass
- CClass定义了自己的Interface_1版本
- 有一个BClass的指针,指向一个CClass的对象,某个操作中调用了Interface_1(CClass的实现版本)
很曲折哈?希望我下次读的时候还能看懂@#¥%
- DClass由于只是定义了一个指向AnotherAClass的指针,那么在定义DClass的文件中就不需要include AClass或AnotherAClass的头文件。于是就避免了编译依赖(compilation dependecies)
而BClass因为是继承了AClass,在BClass的文件中就需要加上AClass的头文件,也就不可避免的产生了编译时的依赖。
对于EBO(Empty Base Optimization)的情况,私有继承就显现出了它的优势。
所谓EBO就是这样的一种情况,有一种特殊的类,它没有非静态数据成员(non-static data member),也没有虚函数(于是不会需要空间存储虚表)。
所以这样的一种类其实不占用任何空间,不过因为C++不允许0字节的对象存在,而且很多编译器都会添加额外的空间来实现字节对齐,于是这种特殊的类的实际大小应该是1个char对象的大小。
在这种类中,往往会有很多typedef,enum,静态数据成员或者是非虚函数。所以他们还是有价值的。
需要在“有一个”关系中利用这种类的时候,如果采用composition,那么根据上面的结论,就需要付出额外的空间来“存放”这个本来不占空间的类。
然而如果是私有继承呢,就可以避免这种情况。
(最后是我的想法:这么精打细算……现在貌似很少人会用到这个EBO了吧?如果真的需要,就再回到原书中看看例子吧。)