第5章:构造、析构、拷贝语意学
可以定义和调用一个纯虚函数,不过只能被静态的调用(通过类作用域运算符),不能通过虚拟机制调用。
你声明了一个纯虚析构函数,就必须定义它。因为在你提供了声明前提下,每一个derived class destructor会被编译器加以扩展,以静态调用的方式调用其“每一个virtual base class”以及“上一层base class”的destructor。因此,只要缺乏任何一个base class destructor的定义,就会导致链接失败。
读到这儿,你可能会有疑问,对于普通的类(没有虚机制参与进来,派生类的析构函数中也会逐一调用基类的析构函数)我们也没有提供它析构函数的,那为什么不会导致链接失败呢?这是因为你没有写析构函数,编译器会默默给你提供一个,以便于在后面的派生类的析构函数中调用它。这里的重点是你没有提供,也就是说你没有声明,如果你声明了,但没有提供定义,同样会导致链接失败。
对于基类该不该将虚函数定义为
const的,作者不建议。因为derived class中可能会修改自己的数据成员。
5.1 无继承情况下的对象构造
当编译器遇到这样的定义:
1 | Point global; |
在C之中,global被视为一个“临时性的定义”,因为它没有明确的初始化操作。一个“临时性的定义”可以在程序中发生多次。那些实例会被链接器折叠起来,只留下单独一个实体,被放在程序data segment中一个“特别保留给未初始化之global objects使用”的空间。由于历史的缘故,这块空间被称为BSS,是Block Started by Symbol的缩写。
而global在C++中被视为完全定义(它会阻止第二个或更多个定义)。C和C++的一个差异就在于,BSS data segment在C++中相对地不重要。C++的所有全局对象都被当作“初始化过的数据”来对待。
为继承做准备
虚函数的引入不仅仅是每个类对象增加了一个vptr,而且会引发编译器对类产生膨胀作用。我们所定义的构造函数,编译器会附加一些代码,以便vptr初始化。合成一个copy constructor和一个copy assignment operator,它们都不是trival。因为,如果point类对象被初始化或以一个派生类对象赋值,bitwise操作就存在问题了,vptr设置会出错。
C++编译器要求编译器尽量延迟nontrivial members的实际合成操作,直到遇到使用场合为止。如果在你的设计中存在很多以传值的方式返回局部类对象,提供一个拷贝构造函数就比较合理,因为这会触发编译器的NRV优化。
5.2 继承体系下的对象构造
constructor可能内带大量的隐藏码,因为编译器会扩充每一个constructor,扩充程度视class T的继承体系而定。一般而言编译器所做的扩充操作大约如下:
- 记录在
member initialization list中的data members初始化操作会被放进constrector的函数本身,并以members的声明顺序为顺序。- 如果有数据成员为类对象,并被列于
member initialization list中,那么任何明确指定的参数都应该传递过去。
- 如果有数据成员为类对象,并被列于
- 如果有一个
member并没有出现在member initialization list之中,但它有一个default constructor,那么该default constructor必须被调用。 - 在那之前,如果
class object有vptr,它(们)必须被设定初值,指向适当的virtual table(s)。 - 在那之前,所有上一层的
base class constructors必须被调用,以base class的声明顺序为序:- 如果
basc class被列于member initialization list中,那么任何明确指定的参数都应该传递过去。 - 如果
base class没有被列于member initialization list中,而它有default constructor(或default memberwise copy constructor),那么就调用之。 - 如果
base class是多重继承下的第二或后继的base class,那么this指针必须有所调整。
- 如果
- 在那之前,所有
virtual base class constructors必须被调用,从左到右,从最深到最浅:- 如果
class被列于member initialization list中,那么如果有任何明确指定的参数,都应该传递过去。若没有列于list之中,而class有一个default constructor,也应该调用之。 - 此外,
class中的每一个virtual base class subobject的偏移量必须在执行期可被存取。 - 如果
class object是最底层(most-derived)的class,其constructors可能被调用;某些用以支持这个行为的机制必须被放进来。
- 如果
说明
书中接下来的章节对上面这些做了详细的叙述。全是重点,就不做总结了,自行看书。
第6章:执行期语意学
很重要,不做总结了,自行看书。