《深度探索C++对象模型》第2章 构造函数语意学
1.Default Constructor(默认构造函数,即没有明显实参的函数)的构造情况
C++编译器在以下4种情况下默认构造函数会被认为是nontrival(有用的),需要由编译器合成出来,只执行编译器所需要的行为:
①含有类对象数据成员,且该成员有默认构造函数;
②基类带有默认构造函数的派生类;
①与②相似,假如类内的普通data member也需要初始化,就需要明确定义构造函数 => 扩张构造函数:先(按声明或继承顺序)调用类成员变量的默认构造函数,在初始化其它data member。
③带有虚函数的类;
不管是自身声明的还是继承得来的虚函数,都会发生两个扩展:(1)类编译时,编译器会产生一个vbtl,内放虚函数地址;(2)对象编译时,编译器会产生一个vptr,指向vbtl。其中,vbtl为虚函数表,vptr为虚函数表指针。
此外,虚函数的调用会被改写,如:widget.filp(); -----> (*widget.vptr[1])(&widget); &widget为this指针。
④虚继承下的类;
与③类似,需初始化虚基类指针。
当然,如果程序有需要,程序员需要自己手动编写类的default constructor。
两个误解:

2.Copy Constructor的构造操作
拷贝构造的默认做法是逐个成员拷贝(memberwise initialization),也就是把对象中每一个内建或派生的data member拷贝到另一个对象中,不过对于其中的member class object,不会直接拷贝,而是递归实现。
这种递归的memberwise initialization是如何实现的呢?答案就是Bitwise Copy Semantics或default copy constructor。
Bitwise Copy Semantics(位逐次拷贝)就是将源类中的成员变量中的每一个都逐次复制到目标类,这时,对指针变量来说就存在问题,指针变量复制了源类中的值,一旦源类开辟的内存空间释放,目标类的该指针就指向了被释放的空间,成为了悬空指针(这就是浅拷贝吧)。
问:编译器在什么情况下产生拷贝构造函数?(即不展现bitwise copy semantics,而使用default copy constructor)
答:
同默认构造函数的四点。
因为在①②情况下,编译器必须将member或base class的 copy constructor 的调用操作安插到被合成的 copy constructor中;
而在③情况下,因为class含有virtual function,编译时需要进行扩张操作:增加vbtl并在class object中创建vptr,这样才能保证vptr被正确地赋值了,否则就去执行其它class的virtual function了;
对于情况④,每一个编译器对于虚拟继承的支持承诺,都是表示必须让“derived class object中的virtual base class subobject位置”在执行期就准备妥当,维护“位置的完整性”是编译器的责任。Bitwise Copy Semantics可能会破坏这个位置,所以编译器必须自己合成出copy constructor。

该Point3d的copy constructor是trivial(无用的),初始化操作会采用bitwise copy,这样效率高且安全,并不需要显式定义拷贝构造函数。
但如果有大量成员变量,可以手动定义copy constructor进行memberwise初始化操作,效率更高,如:

若使用memcpy()或memset(),效率更高:

3.初始化成员列表
以下四种情况,需要(必需)通过初始化成员列表进行参数初始化:
①初始化一个reference member时;
②初始化一个const membert时;
③调用一个base class的constructor,而它拥有一组参数时;
④调用一个member class的constructor,而它拥有一组参数时。
总的来说就是,所有需要定义时初始化的都需要初始化列表。

因为会将代码扩张,产生临时对象——先调用默认构造,再调用赋值函数。
对于内置类型效率差别不大, 但对于类类型最好使用初始化列表——可以直接调用拷贝构造。
- list中初始化的顺序是由class中member的声明顺序决定的,编译器会按照这种顺序对list重排,所以下面的程序会出错:

- list中的成员初始化放在explicit user code之前,如下正确:

- list中可以用可以调用成员函数初始化成员变量,如下正确:

参考博客:
浙公网安备 33010602011771号