下面的程序输出什么?为什么?
有趣的问题
#include <stdio.h>
class Test {
int mi;
public:
Test(int i) {
mi = i;
}
Test() {
Test(0);
}
void print() {
printf("mi = %d\n", mi);
}
};
int main()
{
Test t;
t.print();
return 0;
}
程序运行结果如下图所示:
第 10 行,我们想代码复用,想直接调用有参构造函数来初始化 mi 的值
通过打印,可以看出 mi 是随机值,我们直接调用有参构造函数的操作好像并没有成功
第 10 行并不会去调用到有参构造函数,而是生成一个临时对象,这个临时对象在第 10 行运行结束后,它的生命周期和作用域就结束了,所以 Test的无参构造函数等于空实现,并不会初始化 mi 的值
程序意图:
在 Test() 中以 0 作为参数调用 Test(int i)
将成员变量 mi 的初始值设置为 0
运行结果:
成员变量 mi 的值为随机值
构造函数是一个特殊的函数
是否可以直接调用?
是否可以在构造函数中调用构造函数?
直接调用构造函数的行为是什么?
直接调用构造函数将产生一个临时对象
临时对象的声明周期只有一条语句的时间
临时对象的作用域只在一条语句中
临时对象是 C++ 中值得警惕的灰色地带
解决方案
#include <stdio.h>
class Test {
int mi;
void init(int i)
{
mi = i;
}
public:
Test(int i) {
init(i);
}
Test() {
init(0);
}
void print() {
printf("mi = %d\n", mi);
}
};
int main()
{
Test t;
t.print();
return 0;
}
有时候我们在一个类中定义的多个构造函数可能存在同样的代码,我们想代码复用的话,可以定义一个私有成员函数,构造函数可以调用这个私有成员函数来完成代码复用,类似于这里的 init 函数
程序运行结果如下图所示:
mi 被成功的初始化为 0
编译器的行为
现代 C++ 编译器在不影响最终执行结果的情况下,会尽力减少临时对象的产生!!!
神秘的临时对象
#include <stdio.h>
class Test
{
int mi;
public:
Test(int i)
{
printf("Test(int i) : %d\n", i);
mi = i;
}
Test(const Test& t)
{
printf("Test(const Test& t) : %d\n", t.mi);
mi = t.mi;
}
Test()
{
printf("Test()\n");
mi = 0;
}
int print()
{
printf("mi = %d\n", mi);
}
~Test()
{
printf("~Test()\n");
}
};
Test func()
{
return Test(20);
}
int main()
{
Test t = Test(10); // ==> Test t = 10;
Test tt = func(); // ==> Test tt = Test(20); ==> Test tt = 20;
t.print();
tt.print();
return 0;
}
程序运行结果如下图所示:
我们通过打印可以看出第 39 行调用了一次有参构造函数,第 40 行调用了一次有参构造函数。但这是编译器优化后的结果
编译器会在不影响最终执行结果的情况下,尽力减少临时对象的产生,所以,第 39 行,编译器会优化为 Test t = 10;第 40 行,编译器会优化为 Test tt = 20
编译器不进行优化的话,第 39 行,Test(10) 会产生一个临时对象,会调用到一次有参构造函数,然后用这个临时对象来初始化对象 t,会调用到一次拷贝构造函数; 第 40 行,进入 func 函数时,Test(20) 会生成一个临时对象,会调用到一次有参构造函数,func 函数返回后,会进行一次复制,又会生成一个临时对象,会调用到一次拷贝构造函数,最后,用这个临时对象来初始化对象 tt,会调用到一次拷贝构造函数。
当我们的构造函数的实现比较复杂、耗时时,我们就必须考虑减少临时对象的产生,减少构造函数被调用的次数
小结
直接调用构造函数将产生一个临时对象
临时对象是性能的瓶颈,也是 bug 的来源之一
现代 C++ 编译器会尽力避开临时对象
实际工程开发中需要人为的避开临时对象