带有Default Constructor的Member Class Object
如果一个类没有任何constructor,但它内含一个member object,而后者有default constructor,那么这个类的implicit default constructor就是nontrivial,编译器会为此合成出一个default constructor。只是这个合成操作只有在constructor真正需要被调用时才会发生。示例如下:
class Foo {public: Foo()};
class Bar {public: Foo foo; char * str; };
void func()
{
Bar bar; //此处Bar::foo必须要被初始化
}
于是编译器可能合成出以下default constructor:
inline Bar::Bar()
{
foo.Foo::Foo();
}
可以看出编译器只合成出对member object进行初始化的default constructor,至于其他数据成员如str指针并没有被初始化,它的初始化程序应由程序员编写完成。于是程序员可以编写以下的default constructor:
Bar::Bar() {str = NULL;}
那么此时在已有default constructor的情况下,编译器该如何操作呢?没错,如果类中内涵一个或多个member objects,那么编译器会对每个已存在的constructors进行扩张,如下所示:
Bar::Bar()
{
foo.Foo::Foo(); //扩张的代码
//...如果有其他member objects,则按声明顺序进行扩展
str = NULL;
}
带有Default Constructor的基类
如果一个没有任何constructor的类派生自一个带有default constructor的base class,那么这个派生类的default constructor会被视为nontrivial,因此需要进行合成,也就是调用基类的default constructor。同样当派生类已有多个非default的constructors时,编译器会对现有的每一个constructor进行扩张,将基类的default constructor添加进去。
带有一个Virtual Function的类
在编译期间会发生如下两个扩张操作:
- 一个virtual function table(vtbl)会被编译器产生出来,保存类的virtual function地址。
- 在每一个class object中,一个额外的pointer member(vptr)会被编译器合成,指向vtbl的地址。
为了使得上述操作生效,编译器必须为每一个派生类对象的vptr设定初值。对于类所定义的每一个constructor,编译器会插入一些代码来做这样的事情(详见《C++对象模型》5.2节)。
带有一个虚基类的类
如下代码:
class X {public: int n};
class A : public virtual X {...};
class B : public virtual X {...};
class C : public A, public B {public: int k; };
//无法在编译期间决定出pa->X::i的位置
void foo(const A* pa) { pa->n = 1024; }
void main ()
{
foo(new A);
foo(new C);
}
编译器无法固定住foo()之中“经由pa而存取的X::i”的实际偏移位置,因为pa的真正类型可以改变。编译器必须使X::i可以延迟到执行期才决定下来。通过使用reference或pointer来存取一个virtual base class可以实现该目标。于是上述操作被编译器转变如下:
//__vbcx就是编译器所产生的指针
void foo( const A* pa) { pa->__vbcx->n = 1024; }
这个指针也必须在构造期间进行初始化,所以编译器需要在每个constructor中插入初始化的代码。如果没有声明任何constructor,那么编译器必须为它合成一个default constructor。
总结
在看本节之前存在的误解:
- 任何class如果没有定义default constructor,就会被合成出一个来。
- 编译器合成出来的default constructor会明确设定“类内每一个数据成员的默认值”。
显然以上没有一个是正确的。