(1)默认构造函数的建构
如果一个类没有定义任何构造函数,那么编译器会在需要的时候产生一个构造函数。要注意,这里的需要是编译器的需要,而不是程序员的需要,被编译器产生的构造函数只执行编译器需要的行动而不负责程序的需要。在下面四种情况下,编译器为应对其“需要”而产生合成构造函数,这里用合成而不用产生是因为有的地方已经存在构造函数但编译器还必须根据其需要在已有的构造函数中添加相应的代码。
case 1:带有default constructor的member object
如果一个类没有任何constructor,但内含一个member object,而该member object含有default constructor,那么编译器需要为此class合成一个default constructor,但是这个合成操作只在constructor真正需要被调用时才发生。例如:
1 class Foo { public : Foo(); Foo(int); ...}; 2 class Bar { public : Foo foo; char* str; }; 3 4 void fool_bar() 5 { 6 Bar bar; // Bar::foo must be initialized here。 7 ... 8 }
上面构造bar对象时,编译器会合成一个默认构造函数,该函数会调用Foo的默认构造函数来处理foo对象,但是不会产生任何代码来初始化Bar::str。初始化Bar::foo是编译器的责任,但是初始化str则是程序员的责任。
如果程序员为初始化str而编写如下构造函数:
1 Bar::Bar() 2 { 3 str = NULL; 4 }
这里由于默认构造函数已经被编译出来了,所以编译器不再合成新的默认构造函数,但是会扩张已有的默认构造函数,在其中安插一些代码,在user code被执行之前,先调用必要的default constructors。因此上述代码通过编译器扩张之后的样子可能如下:
1 Bar::Bar() 2 { 3 foo.Foo::Foo(); //compiler code 4 str = 0; 5 }
如果类中有多个class member objects都需要constructor初始化操作,那么编译器将以“member objects 在class中的声明次序”安插调用constructors的代码。
case 2:带有default constructor的base class
基本同case 1,如果没有默认构造函数,编译器会合成一个默认构造函数,并在其中调用基类的default constructors,如果已有constructors,那么编译器会扩张每一个constructor,在其中安插调用base class 的default constructor的代码。
case 3:带有virtual function的class
此时编译器会为类产生一个virtual function table存放virtual functions的地址,并在构造函数中为每一个类对象合成出一个vptr,指向vtbl。
case 4:带有一个virtual base class的class
看如下类的定义:
1 class X { public: int i; }; 2 class A : public virtual X { public: int j; }; 3 class B : public virtual X { public: double d; }; 4 class C : public A, public B { public: int k; }; 5 6 void foo(const A* pa) { pa->i = 1024; }
第6行中的经由pa而存取的X::i的实际偏移地址一直到执行期才能确定,因为pa的真正类型也即动态类型是可以变化的,对此,编译器必须改变“执行存取操作”的代码,使X::i可以延迟至执行期才决定下来。对此,解决方法时为每一个派生类的virtual base class安插一个指针,所有“经由reference或pointer来存取一个virtual base class”的操作都可以通过相关指针完成。如第6行代码可改为:
void foo(const A* pa) { pa->_vbcx->i = 1024; }
其中,_vbcx表示编译器产生的指针,指向virtual base class X.编译器会为每一个继承了virtual base class的类的构造函数中安插这样的“允许每一个virtual base class的执行期存取操作”的代码,如果类没有任何构造函数,编译器会合成默认构造函数并做这样的工作。
(2)总结
对于上述四种情况,编译器会合成默认构造函数或者在已有的构造函数中安插代码以满足编译器的需要,对于不满足上述四种情况且没有任何构造函数的类,实际情况是编译器会暗地里声明一个完全没有用的默认构造函数。
再次强调,编译器合成构造函数是为了满足其自身的需要而非程序的需要,它只初始化基类成分或类成员,对于其他的成员如整数、整数指针、整数数组等都不会被初始化,这些成员需要程序员自己编写代码实现。