C++中在类实例化一个对象时,类的构造函数会被“自动调用”,从而完成对象的一些初始化工作。如果类没有提供显式的构造函数,我们就认为编译器会构造一个默认构造函数,而在C++annotated reference manual中告诉我们,默认构造函数会在“需要的时候”被编译器产生出来,那么何时为“需要的时候”呢?接下来就介绍一下“需要的时候”:
1. 当一个类A有成员变量为另一个类B的对象时(memberclass),如果成员变量的类B含有默认构造函数时且这个类A没有显式的构造函数,如果在需要的地方(实例化一个对象的地方)那么编译器会合成一个默认的构造函数。但是这样又出现一个问题:在不同的编译模块编译器可能会合成多个默认构造函数,编译器解决的方法是把合成的默认构造函数声明为inline的。这样inline函数有静态链接,不会被编译模块以为外者看到。如果函数很复杂,那么就合成一个non-inlinestatic函数:
class B
{
public:
B()
{
cout << "This is a B default constructor" << endl;
}
~B()
{}
};
class A
{
public:
B obj;
char* str;
};
int main()
{
A a;
return 0;
}
测试结果为:
说明编译器为A合成了一个默认构造函数,并在其中安插了调用B默认构造函数的语句,但是需要注意的是合成的默认构造函数不会初始化str,这是程序员的事情,那么正确的做法是我们自己声明显式的默认构造函数:
class A
{
public:
A()
{
str = NULL;
cout << "This is a A explicit default constructor" << endl;
}
~A()
{}
public:
B obj;
char* str;
};
那这时编译器为了初始化B会合成一个默认构造函数,但是我们已经显式提供了一个默认构造函数,那编译器如何实现的呢?编译器会在我们提供的默认构造函数中最前面安插进去调用B默认构造函数的语句。
上面的例子运行结果为:
说明了在我们提供的默认构造函数中,编译器安插了调用B默认构造函数的语句。如果在类中有多个成员数据都是含有默认构造函数的类的对象时,调用这些类的默认构造函数的顺序为它们声明的顺序:
class B
{
public:
B()
{
cout << "This is a B default constructor" << endl;
}
~B()
{}
};
class C
{
public:
C()
{
cout << "This is a C default constructor!" << endl;
}
~C()
{}
};
class A
{
public:
B b;
C c;
};
测试结果为:
2.当一个类继承自带有默认构造函数的基类时,且这个类没有任何的构造函数,那么编译器会合成一个默认的构造函数,来调用基类的默认构造函数:
class A
{
public:
A()
{
cout << "This is a A default constructo!" << endl;
}
~A()
{}
};
class B : public A
{
};
B b;
测试结果为:
如果在一个类,既是由一个含有默认构造函数的基类派生而来,且也有成员变量为含有默认构造函数的类对象,那么会合成一个默认的构造函数先调用基类的默认构造函数,然后调用成员变量的类的默认构造函数。
class B
{
public:
B()
{
cout << "This is a B default constructor!" << endl;
}
~B()
{}
};
class C : public A
{
public:
B obj;
};
测试结果为:
3.在有虚函数的类中,如果没有提供默认的构造函数,那么编译器会合成一个默认构造函数用来初始化虚函数表指针,这一点首先需要明确一个问题:在含有虚函数的类中,编译器会在编译时期做两个扩充动作:
a.生成一个虚函数表,用来存放虚函数地址
b.在类中安插一个vptr用来指向虚函数表头指针
这两个扩充动作的作用在于C++多态的实现,在运行期,程序会根据虚函数表来找到相应的函数,从而实现多态。如果我们提供了显式的构造函数,那么编译器会在我们提供的每个显式构造函数的开始安插初始化vptr的语句。
4.带有一个虚基类的类,如果没有提供默认的构造函数,那么编译器会合成一个默认的构造函数,正确的初始化虚基类指针(或位置)。如果类提供了显式的构造函数,那么编译器会在每个显式的构造函数中安插初始化基类指针(或位置)的语句。
总结:在C++中,需要编译器合成默认构造函数的情况就以上四种,编译器合成默认构造函数总是因为一定的原因——使得程序能够正确的初始化某些东西,而不是平常所想象的那样:”类没有显式提供默认构造函数,那么编译器就会合成一个“。所以C++新手一般有两个常见的误解(Inside the c++ object model的47页):
a.任何类如果没有定义默认构造函数,就会被合成出来一个。
b.编译器合成出来的默认构造函数会明确设定类里面的每一个数据成员的默认值。
如你所见,没有一个是真的!