什么时候编译器会合成默认构造函数?
trivial default constructor
当一个类没有任何用户自定义的构造函数时,它所拥有的是编译器声明的一个implicit trivial default constructor,这些构造函数实际上并不会被编译器合成出来。
编译器的需要
在四种情况下,为了满足编译器的需要,编译器会合成一个implicit nontrivial default constructor,以设置好基类,类对象,虚函数表,虚基类的信息。
(1)类对象有默认构造函数时,编译器会为该类合成一个有用的构造函数,以调用类对象的构造函数,为其设定好初始值;
(2)类的基类有默认构造函数时;
(3)当类中有virtual function或类的基类有virtual function时,由于在编译的时候编译器会为这种类产生一个虚函数表,并且为每一个对象安插一个虚指针以指向虚函数表,因此编译器会合成一个默认构造函数,并在其中正确的设置好虚指针的值。
(4)带有虚基类的class
虚基类的实现方法在不同编译器上区别较大,但是共同点是必须是virtual base class在每一个派生类的位置,能够在执行期准备妥当。虚基指针也是在构造函数内安置妥当。
程序员的需要
对于类的内置类型成员,由编译器合成的构造函数不会去设置初始值,使这些值正确的被初始化,程序员必须提供显示的构造函数来完成。
copy constructor和copy assignment operator
Default Memberwise Initialization(默认的深拷贝)
当一个类没有提供显示的拷贝构造函数时,会以深拷贝的方式初始化每一个类对象。只有当一个类不展现bitwise copy semantics时,编译器才会为之产生拷贝构造函数。
不展现bitwise copy的四种情况与合成nontrivial constructor的情况相同。前两者是为了转调用或类对象或基类的拷贝构造函数,后两个是为了调整vptr和vbtr的值。
参数传入,NRV优化
函数参数的初始化
void foo(X x0);//函数的声明
//调用形式
X xx;
foo(xx);
//可能的转化形式
X _tmp;//生成一个临时对象
_tmp.X::X(xx);//拷贝xx对象
foo(_tmp);//void foo(X x0)被改写为void foo(X& x0)
一种参数的初始化行为为产生一个临时对象,并执行copy constructor拷贝参数的值,而后把这个临时对象引用到函数之中,最后被自动析构。
返回值的初始化和NRV优化
X bar()
{
X xx;
//处理xx
return xx;
}
对应的伪代码为
void bar(X& _result)
{
X xx;
//处理xx
_result.X::X(xx);
return;
}
当编译器支持NRV优化时,会以xx在指定变量上拷贝构造,并直接处理_result
void bar(_result)
{
_result.X::X(xx);
//直接处理_result
return;
}
构造函数初始化列表
class String;
class Word
{
public:
Word()
{
_name = 0;
_count = 0;
}
private:
String _name;
int _count;
};
Word::Word()
{
_name.String::String();
String tmp = String(0);
_name.String::operator=(tmp);
tmp.String::~String();
_cnt = 0;
}
即程序会先构造变量,再拷贝赋值。因此用参数化列表,直接构造的效率会更高。类对象的初始化顺序和初始化列表中的顺序无关,取决于其在类中的声明顺序。
(1)初始化一个引用对象;因为引用必须在声明的时候指定绑定的值;
(2)初始化一个const变量,因为const 变量不能修改值;
(3)调用一个base class的constructor,而他有一组参数的时候;
(4)调用member class的constructor,而他有一组参数的时候。