class Empty{};
class HoldsAnInt {
private:
int x;
Empty e; //应该不需要任何内存
};
你会发现sizeof(HoldsAnInt)> sizeof(int);一个Empty成员变量竟然要求内存。在多数编译器中sizeof(Empty)获得1,因为面对“大小为零的独立对象”,通常c++官方勒令默默安插一个char到空对象内。然而齐位需求(alignment)可能造成编译器为类似HoldsAnInt这样的class加上一些衬垫(padding),所以有可能HoldsAnInt对象不只多一个char大小,实际上放大到多一个int。
独立(非附属)这个约束不适用于derived class对象内的base class成分,因为它们并非独立。如果你继承Empty,而不是内含一个那种类型的对象:
class HoldsAnInt: private Empty{
private:
int x;
};
几乎可以确定sizeof(HoldsAnInt) == sizeof(int)。这是所谓的EBO(empty base optimization:空白基类最优化),如果你是一个库开发成员,而你的客户非常在意空间,那么值得注意EBO。另外一个值得知道的是,一般EBO只在单一继承(而非多继承)下才可行。
现实中的“Empty”class并不是真的empty。虽然他们从未拥有non-static 成员变量,却往往内含typedefs, enums, static成员变量,或non-virtual函数。stl就有许多技术用途的empty classes,其中内含有用的成员(通常是typedefs),包括base classes unary_function和binary_function,这些是“
用户自定义的函数对象”通常都会继承的classes。感谢EBO的广泛实践,这样的继承很少增加derived classes的大小。
尽管如此,大多数class并非empty,所以EBO很少成为private继承的正当理由。复合和private继承都意味着is-implemented-in-term-of,但复合比较容易理解,所以无论什么时候,只要可以,还是应该选择复合。
请记住:
1.private继承意味is-implementation-in-terms of(根据某物实现出)。她通常比复合级别低。但是当derived class需要访问protected base class的成员,或需要重新定义继承而来的virtual函数时,这么设计是合理的。
2.和复合不同,private继承可以造成empty base最优化。这对致力于“对象尺寸最小化”的程序库开发者而言,可能很重要。
条款40:明智而审慎地使用多重继承
一旦涉及多重继承(multiple inheritance;MI):
程序有可能从一个以上的base class继承相同名称(如函数、typedef等)。那会导致较多的歧义机会。例如:
class BorrowableItem {
public:
void checkOut();
};
class ElectronicGadet {
private:
};
class MP3Player: public BorrowableItem
public ElectronicGadet
{...};
MP3Player mp;
mp.checkOut();//歧义,调用的是哪个checkOut?
即使两个之中只有一个可取用(ElectronicGadet是private)。这与c++用来解析重载函数调用的规则相符:在看到是否有个函数可取之前,c++首先确认这个函数对此调用之言是最佳匹配。找出最佳匹配才检验其可取用性。本例的两个checkOut有相同的匹配程度。没有所谓最佳匹配。因此ElectronicGadget::checkOut的可取用性就从未被编译器审查。
为了解决这个歧义,必须明白指出你要调用哪个base class内的函数:
mp.BorrowableItem::checkOut();
你当然也可以明确调用ElectronicGadget::checkOut(),但然后你会获得一个“尝试调用private成员函数”的错误。
当即称一个以上的base classes,这些base classes并不常在继承体系中有更高级的base classes,因为那会导致要命的“钻石型多重继承”:
class File{...};
class InputFile: public File {...};
class OutputFile: public File{...};
class IOFile: public InputFile,
public OutputFile
{...};
任何时候只要你的继承体系中某个base class和某个derived class之间有一条以上的想通路线,你就必须面对这样一个问题:是否打算让base class内的成员经由每一条路径被复制?假设File有个成员变量fileName,那么IOFile应给有两份fileName成员变量。但从另一个角度来说,简单的逻辑告诉我们,IOFile对象只有一个文件名称,所以他继承自两个base class而来的fileName不能重复。
c++的缺省做法是执行重复。如果那不是你要的,你必须令那个带有此数据的base class(也就是File)成为一个virtual base class。必须令所有直接继承自它的classes采用“virtual继承”:
class File{...};
class InputFile: virtual public File {...};
class OutputFile: virtual public File{...};
class IOFile: public InputFile,
public OutputFile
{...};
c++标准程序库内含一个多重继承体系,只不过其class是class template: basic_ios,basic_istream,basic_ostream和basic_iostream。
从正确行为来看,public继承应该总是virtual。如果这是唯一一个观点,规则很简单:任何时候当你使用public继承,请改用virtual public继承。但是,正确性并不是唯一观点。为避免继承来的成员变量重复,编译器必须提供若干幕后戏法,其后果就是:使用virtual继承的那些classes所产生的对象往往比使用non-virtual继承的兄弟们体积大,访问virtual base classes的成员变量时,也比访问non-virtual base classes成员变量速度慢。
virtual继承的成本还包括其他:支配“virtual base classes初始化”的规则比起non-virtual base的情况远为复杂和不直观。virtual base 的初始化责任是由继承体系中的最底层(most derived)class负责,1、class若派生自virtual base class而需要初始化,必须认知其virtua