条款11 编译器会在类中放东西
C程序员习惯于了解所使用的struct的内部结构和布局的一切细节,并且习惯于编写依赖于特定布局的代码。Java程序员习惯于在对所使用对象的结构布局懵懂无知的情况下编程(www.cppentry.com),并且往往会认为一旦开始使用C++(www.cppentry.com)编程(www.cppentry.com),那种懵懂无知的日子就结束了。实际上,在C++(www.cppentry.com)中,安全和可移植的编程(www.cppentry.com)实践,确实需要对类对象的结构和布局保持某种程度的“健康的不知情”。
对于一个类而言,并非总是“所见即所得”。举个例子,大多数C++(www.cppentry.com)程序员都知道,如果一个类声明了一个或多个虚函数,那么编译器将会为该类的每一个对象插入一个指向虚函数表的指针(事实上,标准并没有保证一定如此,但所有现有的C++(www.cppentry.com)编译器都是采用这种方式来实现虚函数机制的)。然而,处于堪可胜任和游刃有余水平之间的C++(www.cppentry.com)程序员通常会假定,不同平台之间的虚函数表指针的位置是相同的,因此编写依赖于这种假定的代码,从而产生致命的错误。实际上,有些编译器将指针置于对象的开头,还有一些则将其放在对象的末尾,而且,如果涉及多重继承,若干个虚函数表指针就可能会散布于对象之中。所以,永远不要做这样的假定,永远不要想当然!
且慢,我的话还没说完。如果使用了虚拟继承(virtual inheritance),对象将会通过嵌入的指针、嵌入的偏移或其他非嵌入的信息来保持对其虚基类子对象(virtual base subobject)位置的跟踪。因此,即便类没有声明虚函数,其中还是有可能被插入了一个虚函数表指针!我有没有给你说过这件事:不管类的数据成员的声明顺序如何,编译器都被允许(有节制地)重新安排它们的布局?有什么办法来阻止这种疯狂的行为吗?
有!如果确保一个类类型像一个C struct非常重要,就可以定义一个POD(Plain Old Data,朴素的旧式数据)。自然而然,内建的类型,比如int、double以及诸如此类的东西,都是POD,而且C struct或类似union的声明,也都是POD。
- struct S { // 一个POD struct
- int a;
- double b;
- };
这样的POD可以像对应的C struct那样安全地进行操纵(至于说到底有多安全,在C++(www.cppentry.com)中就像在C中一样值得怀疑)。然而,如果计划对POD进行低层的处理,那么,在对代码进行维护的过程中,始终保持其为POD很重要,否则所有的赌注将会输得精光:
- struct S { // 不再是一个POD struct!
- int a;
- double b;
- private:
- std::string c; // 进行了一些维护
- };
如果不乐意于只能处理POD,那么,编译器的这种干预对应该如何操纵类对象有何启示呢?启示就是,应该在高层操纵类对象,而不应该把它当成一组位的集合。在不同的平台上,高层的操作将会做相同的事情,但它们的底层实现可能并不相同。
例如,如果希望复制一个类对象,那么永远都不要使用memcpy这样的标准内存块复制函数(或手工编写的等价代码),因为这种函数是用来复制存储区,而不是用来复制对象的(“placement new” [条款35]讨论了二者之间的区别)。相反,应该使用对象的初始化或赋值操作。对象的构造函数是编译器建立隐藏机制的地方,该隐藏机制实现对象的虚函数以及诸如此类的东西。仅仅往未被初始化的存储区中塞入一大把比特的做法往往无法达到预计的效果。同样的道理,将一个对象复制给另外一个对象时,必须小心不要覆盖这些内部类的机制。例如,赋值操作永远不应该改变对象的虚函数表指针的值,它们由构造函数设置,并且在对象整个生命期内保持不变。简单的“比特冲击”可能还会破坏脆弱的内部结构(参见“复制操作符” [条款13])。
另一个常见的问题是假定一个类的特定成员位于对象中给定的位置。尤其特别的是,会出现一种常见的情况,就是一些聪明过头的代码,要么假定虚函数表指针位于零偏移处(换句话说,是类中的第一个东西),要么假定第一个声明的数据成员位于零偏移处。在半数以上场合下,这两个假定都是不正确的。当然,任何时候两个假定都不可能同时成立:
- struct T { // 不是一个POD
- int a_; // a_的偏移量未知
- virtual void f(); // vptr的偏移量未知
- };
我不想在这个话题上继续絮叨下去,这样的禁忌列表会变得冗长、无趣而乏味。但是,如果你下一次发现对类的内部结构作了低级的假定,请暂停并反省一下,并且让你的思想远离这种“低级”趣味。