[C++]《深度探索C++对象模型》读书笔记 - nontrivial default constructor
C++里,如果程序员没有显式的定义默认构造函数(default constructor),编译器会在需要的时候生成一个,也就是隐式地声明出来。
隐式声明的默认构造函数有两种,一种是trivial(无用的) constructor,什么都不做;另一种是nontrival constructor,编译器合成的是后者。
在四种情况下,编译器需要合成nontrival constructor:
1. 带有"Default Constructor"的成员类对象
class A {public: A(), ... }; class B {public: A a;}; int f() { B b; //此处须调用B的默认构造函数 }
因为B::a是一个member object,且其class A拥有default constructor,所以编译器在需要调用B的默认构造函数时,会为B生成如下的默认构造函数
inline B::b() { // C++伪码 a.A::A(); // 调用a的默认构造函数 }
但需要注意的是,编译器产生的默认构造函数不会初始化基本类型的成员变量。
如果class B中有int类型的变量,其值不会被默认构造函数初始化为0。
还有一种情况,如果class B已经拥有一个默认构造函数,但其中并未对其对象成员进行初始化,如下例:
class B {public: A a, int num}; B::B() {num = 0}
对于这种情况,编译器会扩张已有的constructor,将对象成员的构造过程安插在user code前,像这样:
B::B() { a.A::A(); // 插入的compiler code num = 0; // user code }
另外还需注意的是,编译器所安插的代码会按照“对象在class中的声明顺序”来调用各个constructor,在user code前。
2. "带有Default Constructor"的Base Class
如果一个class派生自一个“带有default constructor”的base class,那个这个class的default constructor需要被编译器合成出来,以调用上一层base class的default constructor。
很多人都了解“子类在调用构造函数时,会先调用父类的构造函数”,这正是因为编译器在我们定义的constructor中进行了上述扩展。
3. "带有Virtual Function"的Class
虚函数表(vtbl)的相关实现机制超出了本文的范围,读者可找相关文章或书藉来学习。
如果class或继承的base class中声明了virtual function(虚函数),编译器会对default constructor进行扩张:
1. 为class产生一个virtual function table。
2. 为每个class object创建一个额外的point member(vptr),指向相应的virtual function table。
此外,编译器还会对“虚函数的调用操作”进行修改,如b.f(),会被改为:
(*(b.vptr[1])) (&b); // 1表示函数f()在vtbl中的索引值,(b.vptr[1])返回了函数f()的地址。 // &b表示交给f()函数调用的this指针,这里涉及到了name magling的知识,读者可自行搜索。
4. "带有一个Virtual Base Class"的Class
虚基类(virtual Base Class)指多继承时,某个base class可能会出现在多个继承路径上。为了防止该基产生多个拷贝,可将基类的继承声明为虚拟的,这样就只会继承基类的一份拷贝。
虽然这样base class只有一份拷贝,但在多态环境下,编译器无法固定”经由某个指针类型而存取的base class中的成员”的实际偏移位置,因为其实际类型是可以改变的。
cfont对这个问题的解决方法是:在derived class object的每一个virtual base class中安插一个指针__vbcX,所有经由引用或指针对virtual base class的操作都可以通过该指针来完成。
而对__vbcX的初始化,正是编译器在default constructor中进行的扩张。
对于这4种情况以外的,且没有声明default constructor的class,我们说它们拥有的是trivial default constructors,实际上它们并没有被合成出来。
所以,“任何class如果没有定义default constructor,都会被生成一个出来”这种说法是错误的。
最后还有一点要注意,生成出来的nontrivial default constructor只对类对象进行初始化,或基本类型(int, double, string)的成员变量还需要程序员来显式的进行初始化。