1 临时对象的产生
1.1 产生了临时对象
首先,我们先看一段代码,我们使用带参数的构造函数实现不带参数的构造函数
//15-1.cpp
#include<stdio.h>
class Test
{
public:
Test(int i) : x(i){}
Test()
{
Test(0); // 调用带参构造函数
}
~Test()
{
printf("~Test()\n");
}
void print()
{
printf("x = %d\n", x);
}
private:
int x;
};
int main()
{
Test t;
t.print();
return 0;
}
不带参数的构造函数调用了带参数的构造函数,将 x 初始化为 0。
编译运行:
$ g++ 15-1.cpp -o 15-1
$ ./15-1
~Test()
x = 21928
~Test()
我们可以看到,类的成员变量 x 并没有被初始化为 0,而是随机值。并且调用了两次析构函数。这是为什么呢?
解析:
- 直接调用构造函数将产生一个临时对象
- 临时对象的声明周期只有一条语句的时间,作用域在一条语句中
1.2 问题解决
那对于上面的代码,如果初始化工作非常复杂,我们又想实现代码复用,应该怎么办呢?
一般我们会声明一个私有的成员函数完成初始化,让构造函数调用它。
//15-2.cpp
#include<stdio.h>
class Test
{
public:
Test(int i)
{
init(i);
}
Test()
{
init(0);
}
~Test()
{
printf("~Test()\n");
}
void print()
{
printf("x = %d\n", x);
}
private:
int x;
void init(int i)
{
x = i;
}
};
int main()
{
Test t;
t.print();
return 0;
}
定义私有初始化函数 init(),然后不同的构造函数调用初始化函数。
$ g++ 15-2.cpp -o 15-2
$ ./15-2
x = 0
~Test()
2 编译器会减少临时对象
- 现代的 C++ 编译器在不影响最终执行结果的前提下,会尽力减少临时对象的产生。
构造函数可能会非常大,减少临时对象可以减少调用构造函数,提高效率。
下面看一示例:
// 15-3.cpp
#include<stdio.h>
class Test
{
public:
Test(int i) : x(i)
{
printf("Test(int):%d\n", i);
}
Test() : x(0)
{
printf("Test()\n");
}
Test(const Test& t)
{
x = t.x;
printf("Test(const Test&:%d\n)", t.x);
}
void print()
{
printf("x = %d\n", x);
}
~Test()
{
printf("~Test()\n");
}
private:
int x;
};
Test func()
{
return Test(20);
}
int main()
{
Test t = Test(10);
Test tt = func();
t.print();
tt.print();
return 0;
}
上面的代码很简单,主要是为了说明现代的 C++ 编译器尽力减少临时对象的产生。
第 36 行,直接使用 Test(10) 将产生一个临时对象,再赋值给 t 又会调用拷贝构造函数。
函数 func() 产生一个临时对象并返回,将返回对象赋值给 tt,也将调用拷贝构造函数。
但是,实际上 g++ 编译器经过优化,这里避免了临时对象的产生,也不会调用拷贝构造函数。所以:
第 36 行等价于 Test t = 10;
第 37 行等价于 Test tt = 20;
$ g++ 15-3.cpp -o 15-3
$ ./15-3
Test(int):10
Test(int):20
x = 10
x = 20
~Test()
~Test()
3 小结
1、直接调用构造函数将产生一个临时对象
2、临时对象是性能瓶颈,也是 bug 来源,现在 C++ 编译器会尽力避开临时对象