“C++类中,如果类设计人员没有为类声明构造函数,那么编译器就会合成一个默认构造函数来初始化类的数据成员” 那么上述这句话对么?答案是:否。
这是C++程序人员常犯的错误(当然是针对我这种新手而言,呵呵),那么原因是什么呢?让我们今天来探讨探讨。
默认构造函数在需要的时候被编译器产生出来,关键字眼在于:在需要的时候,被谁需要?做什么事情?请看下列的程序代码:
class Test
{
public:
int m_iVal;
Test* m_pNext;
}
void Test_bar()
{
Test test;//声明了一个Test对象,从这个程序功能来看,似乎是需要test的所有成员都被清零或者初始化
if(test.m_iVal || test.m_pNext)
// .... do something
}
其实,本段代码类Test的两个成员并不会被初始化,毕竟巧妇难为无米之炊,包含的类对象自己自己都 没得默认构造函数,那编译器也没办法了噻。噢,我听到你在尖叫,不是在需要的时候会合成默认构造函数么?现在怎么不行了?其实问题的本质在于,这个需要是谁需要,在C++中,一种是编译器需要,一个是程序需要。上面的测试函数,初始化是程序需要而已,所以承担责任的应该是设计class Test 的人,因此上述程序并不会合成出一个默认的构造函数。
那么,问题来了,什么情况下会合成默认构造函数呢??答案是当编译器需要的时候才会合成,存在以下四种情况,让我们一个一个的来探讨吧。
情况1:如果一个class没有任何构造函数,但是在它内部有一个成员,而这个成员有默认构造函数,那么编译器这个时候就会为此class合成一个默认构造函数;
例如:
class Foo {
public:
Foo();
Foo(int);
}
class Bar{
public:
Foo foo;
char* str;
}
/
void foo_bar()
{
Bar bar;//Bar::foo会在此初始化,Bar::foo是类Bar的一个成员对象,而类Foo有默认构造函数
if(str ){...... }
}
注:合成的默认构造函数只会初始化bar对象,而它不会初始化str。因为现在初始化Bar::foo是编译器的责任,而初始化str是程序的责任
当然,这个时候你想要初始化str,那你得写一个构造函数来初始化,于是上面的类变成了这个样子:
class Bar{
public:
Bar(){ str = 0;}
public:
Foo foo;
char* str;
}
请注意,一旦设计人员显示的写了构造函数,编译器就没法合成第二个了,直观上来说,Bar::foo对象并没有被初始化,但是编译器还有一个神奇的功能,虽然它不会合成第二个,但是它会扩展现有的构造函数,来初始化包含在该类的数据对象。因此上述的构造函数Bar()扩展后可能是这个样子
Bar::Bar()
{
foo.Foo::Foo();//调用Foo的构造函数来初始化foo对象
str = 0;//现有构造函数的语句
}
现在我们扩展一下,考虑下述情况:一个类中包含了多个其它类的对象,那这个时候编译器扩展或者合成的构造函数的初始化操作是怎样进行的。
答案是:这个时候C++编译器会按照这些类成员对象在该类的声明次序来进行构造函数的扩展。请看下例:
class Test1 {public: Test1();....}
class Test2{public: Test2();.....}
class Test3{public: Test3();....}
class Test{
public:
Test():test2(1024) { number = 2048; }
public:
Test1 test1;
Test2 test2;
Test3 test3;
private:
int number;
}
则这个构造函数Test会被扩张为:
Test::Test():test2(1024)
{
test1.Test1::Test1();
test2.Test2::Test2(1024);
test3.Test3::Test3();
number = 2048;
}
情况2:如果一个类没有任何的构造函数,但是其基类有默认构造函数,那么这个类就会由编译器合成一个默认构造函数。这个情况比较简单,很好理解。
情况3:带有虚函数的类,如果没有任何构造函数,那么编译器会合成默认构造函数;
(1).class 声明或继承一个virtual函数;
(2).class 派生于一个继承链,其中一个或者多个virtual基类;
请看代码:
//抽象基类
class Widget{
public:
virtual void flip() = 0;
}
void flip(const Widget& widget){ widget.flip(); }
class Bell:public Widget{ };
class Whistle:public Widget{ };
//存在如下函数
void foo()
{
Bell b;
Whistle w;
flip(b);
flip(w)
}
编译期间会进行扩张:一个virtual function table 被产生出来;在每个类对象中,还会产生一个额外的pointer member,也就是指向虚函数表地址的指针。
情况4:带有一个虚基类的类,这个我自己也不是很明白,大家可以查阅相关资料进行分析。
还有几点需要注意的是:(1).一个类声明了构造函数,编译器不会合成第二个,而是在此构造函数上进行扩展;
(2).一个类中包含多个带有默认构造函数的类,那么编译器合成的默认构造函数,拷贝构造函数,析构函数等都会以inline的方式完成,因为inline函数 是静态链接,不会被档案以外的人看到
好了,现在来总结下:
以上四种情况能合成默认构造函数,另外,在合成的默认构造函数中,只有基类对象,和内含于类的其它类成员会被初始化,至于本类的其它非静态的数据对象不会被初始化,这些是程序需要,所以和编译器关系不大,需要设计人员显示的进行初始化。