构造函数语义学
默认构造函数
两大误解
- 任何class 如果没有定义default constructor,就会被合成一个出来。
- 编译器合成出来的default constructor 会明确设定“class 内每一个data member 的默认值”
上述两个最常见的看法,都是错误的。
默认构造函数实际上是被编译器需要的,用以实现一些多态机制,而非对类成员进行初始化(程序员的职责)。
四种会合成默认构造函数的情况:
- 类的某一个成员带有默认构造函数
此时需要通过编译器构造的默认构造函数调用成员的默认构造函数。 - 类的父类带有默认构造函数
此时需要通过编译器构造的默认构造函数调用父类的默认构造函数。 - (父类或自身)带有虚函数的类
此时需要通过编译器构造的默认构造函数构造虚函数表和虚函数表的指针(实现多态)。 - 带有虚基类的类
此时需要通过编译器构造的默认构造函数构造虚基类指针,其用于在执行期进行存取操作。
注意:若程序员已经定义了默认构造函数(编译器无法再生成一个),编译器会在已有的默认构造函数前添加一些代码实现其需要的功能(上述提到的四个)。
其他没有构造函数的类实际上不会合成默认构造函数!!!
存在默认构造函数的类定义会被转换如下:
X xx;
//转换如下
//定义(占用内存的行为)
X xx;
//初始化
xx.X::X();
默认拷贝构造函数
三种可能调用拷贝构造函数的情况:
- 对一个对象做明确的初始化操作。
class X{};
X x;
X xx = x;//以一个对象的值初始化另一个对象的值
- 作为参数交给某个函数时
extern void foo(X x);
void bar()
{
X xx;
foo(xx);//作为参数传递
}
- 函数返回值(NRV优化可能就不会再调用拷贝构造函数了)
X foo_bar()
{
X xx;
return xx;//作为返回值
}
和默认构造函数一样,默认拷贝构造函数实际上是为编译器服务的(实现安全的多态),除了处理实现编译器功能的一些内置数据外(例如虚表指针,虚基类表等)对于数据成员,其直接进行Bitwise Copy Semantic(位逐次拷贝),即不会处理深浅拷贝之类的问题(应该由程序员进行处理)。
四种会合成默认拷贝构造函数的情况:
- 类的某一个成员带有拷贝构造函数
此时需要通过编译器构造的默认拷贝构造函数调用成员的拷贝构造函数。 - 类的父类带有拷贝构造函数
此时需要通过编译器构造的默认拷贝构造函数调用父类的拷贝构造函数。 - 带有虚函数的类
此时需要通过编译器构造的默认拷贝构造函数构造虚函数表(割裂时实现正确多态)。 - 带有虚基类的类
此时需要通过编译器构造的默认拷贝构造函数构造虚基类指针,其用于在执行期进行存取操作。
程序转化语意学
上一小节提到了三种调用拷贝构造函数的地方,其中第三种在NRV优化下将调用构造函数而非拷贝构造函数
- 明确的初始化
X x0;
X x1(x0)
X x2 = x0;
X x3 = X(x0);
//转换为下
//定义被重写,初始化操作被剥除
X x1;
X x2;
X x3;
//编译器安插拷贝构造函数
x1.X::X(x0);
x2.X::X(x0);
x3.X::X(x0);
- 参数的初始化
void foo(X x0);
X xx;
foo(xx);
//转换为下
//改变函数声明
void foo(X& x0);
//改变调用方式
X xx;
X __temp0;
__temp0.X::X(xx);
foo(__temp0);
- 返回值的初始化——若NRV优化,不再使用拷贝构造函数
X bar()
{
X xx;
return xx;
}
//NRV优化前
void bar(X& __result)
{
X xx;
xx.X::X();
__result.X::X(xx);
return;
}
//NRV优化后
void bar(X& __result)
{
__result.X::X();
return;
}
NRV(Named Return Value)优化是编译器在函数返回某个在其内部定义的变量时所做的优化(内部变量的类型和返回类型相同),已经较为普遍的存在。
成员们的初始化列表
四种必须使用初始化列表的情况:
- 初始化一个reference member。
- 初始化一个const member。
- 调用一个base class 的constructor且它拥有一组参数。
- 调用一个member class的constructor且它拥有一组参数。
注意编译器对初始化列表一一处理但可能会改变顺序!!!
例如:编译器按照成员的声明顺序进行初始化而非在初始化列表中的顺序。不过,可以保证的是初始化列表在用户的函数代码之前运行。并且由于this指针已经存在了,所以在初始化列表或者构造函数体内可以使用成员函数(注意相关数据顺序逻辑,容易出现问题日)。
总结
这是我自己整理的学习笔记,主要用于自我复习。如果有大佬也看到了这个并且发现了谬误,欢迎email me at lonelytaoist@qq.com
。