隐式创建默认构造函数的迷惑(一)
《深度探索C++对象模型》读书笔记
飘飘白云
《深度探索C++对象模型》(以后简称《深探》)书中如此给新手提醒:
“C++新手一般有两个常见的误解:
1, 任何 class 如果没有定义 default constructor ,就会被合成出一个。
2, 编译器合成出来的 default constructor 会明确设定 “ class 内每一个 data member 的默认值”。
如你所见,没有一个是真的!”。
看了这一段,是不是觉得大学里教C++的就是糊弄小朋友的,如果不是见到这段话,我还会傻乎乎地相信,每个类如果没有定义默认构造函数,就会自动创建一个,并使用“零值”初始化数据成员。
先看个例子:
#include <iostream>
using namespace std;
class Foo
{
public :
int val;
// Foo()
// {
// };
virtual ~Foo()
{
};
protected :
private :
};
int main()
{
Foo bar ;
if ( bar.val == 0)
{
cout << "Data member val has been initialized to 0" << endl;
}
else
{
cout << "Data member val hasn't been initialized to 0" << endl;
}
}
在VS下编译运行输出:Data member val hasn't been initialized to 0。 如果把被屏蔽的那个空构造函数恢复,输出结果就是另外一回事了。
那么到底在什么情况下,C++编译器会为我们创建默认构造函数,并初始化数据成员呢?C++ Standard〔ISO-C++95〕里面这么说的:
“对于 class X ,如果没有任何用户声明的构造函数,那么会有一个默认构造函数被隐式声明出来…… 一个被隐式声明出来的默认构造函数将是一个没什么用的(trival)构造函数。”
这里所说的“没什么用的(trival)”是指仅仅是为了编译器编译的需要,而不是为了程序需要(比如变量必须初始化)而创建这么一个构造函数。看了上面这段话,感觉前面的提醒似乎有点不对,呵呵,后面还有说明:
在四种情形下,“编译器必须为未声明constructor的classes 合成一个 default constructor”,C++标准把那些合成物成为 隐式的有用的(nontrival)default constructor,它们只能满足编译器的需要。
在这四种情况之外而有没有声明任何 constructor 的 classes ,拥有的是隐式的没什么用的( trivial ) default constructor ,实际上它们并不会被合成出来。
下面我们依次来说明这四种情形A,B,C和D。<待续>
情形A:拥有成员对象,并且这个成员对象所属的类拥有默认构造函数。
一个类没有任何构造函数,但是它内含有一个成员对象,而这个成员对象有默认构造函数,那么编译器就会在必要的时候为这个类隐式合成一个有用的(nontrival)构造函数。
还是来看代码说的清楚些:
#include <iostream>
using namespace std;
class Foo
{
public :
// Foo()
// {
// cout << "Default constructor of Foo!";
// };
Foo(int a){
iFooVal = a;
cout << "Common constructor of Foo!";
};
~Foo(){
};
int iFooVal;
};
class Bar
{
public :
Foo foo;
int iBarVal;
~Bar()
{
};
};
int main()
{
Bar bar;
if (bar.foo.iFooVal == 0)
{
cout << "Member data of foo has been initialized!"<< endl;
}
if (bar.iBarVal == 0)
{
cout << "Member data of bar has been initialized!" << endl;
}
}
在这段代码里,我把成员对象Foo的默认构造函数屏蔽了,按照上面的解释,应该是会报错,因为类Bar不会合成一个隐式的有用的默认构造函数。编译运行,看看编译器怎么说:
error C2512: 'Bar' : no appropriate default constructor available
呵呵,不出所料!如果我们恢复Foo的默认构造函数Foo(),则编译运行输出:
Default constructor of Foo!
注意:Bar和Foo的数据成员并没有因此而初始化为0!
下来我们来深入探索下在Foo有默认构造函数的情形下,声明Bar对象时被合成的那个隐式有用的构造函数做了些什么:
它会按照有显式默认构造函数的成员对象在类中声明的顺序依次调用这些成员对象的显式默认构造函数,除此之外它什么也不做。
那么为Bar合成的那个隐式构造函数就应该看起来象这个样子:
inline Bar::Bar()
{
foo.Foo::Foo();
}