第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
章:执行期语意学
很重要,不做总结了,自行看书。