神秘的临时对象(上)
有趣的问题:
下面的程序输出什么,为什么?
1 #include <stdio.h>
2
3 class Test {
4 int mi;
5 public:
6 Test(int i) {
7 mi = i;
8 }
9 Test() {
10 Test(0);
11 }
12 void print() {
13 printf("mi = %d
", mi);
14 }
15 };
16
17
18 int main()
19 {
20 Test t;
21
22 t.print();
23
24 return 0;
25 }
程序意图:
在Test()中以0作为参数调用Test(int i)
将成员变量mi的初始值设置为0
运行结果:
成员变量mi的值为随机值
运行程序后,你会发现mi的值是一个随机数,为什么会出现这种情况呢?
思考:
构造函数是一个特殊的函数
是否可以直接调用?
是否可以在构造函数中调用构造函数?
直接调用构造函数的行为是什么?
答案:
直接调用构造函数将产生一个临时对象
临时对象的生命周期只有一条语句的时间
临时对象的作用域只在一条语句中
临时对象是C++中值得警惕的灰色地带
神秘的临时对象(中)
分析:
Test() {
Test(0); //在此处将产生一个临时对象,临时对象的声明周期只有该行语句的生命周期,过了该条语句,这个临时对象将被析构,并且临时对象没有名字。
没有名字意味着它的作用域仅在该行代码中,过了这行代码,它就无法被访问到了。所以从生命周期和作用域来说,这里的临时对象几乎没有半毛钱关系的。
}
这段代码的本意是想使用代码复用,这种思想没有错。但是如何解决临时对象这个问题呢?
提供一种解决思路:
1 #include <stdio.h>
2
3 class Test {
4 int mi;
5
6 void init(int i)
7 {
8 mi = i;
9 }
10 public:
11 Test(int i) {
12 init(i);
13 }
14 Test() {
15 init(0);
16 }
17 void print() {
18 printf("mi = %d
", mi);
19 }
20 };
21
22
23 int main()
24 {
25 Test t;
26
27 t.print();
28
29 return 0;
30 }
神秘的临时对象(下)
编译器的行为
现代C++编译器在不影响最终执行结果的前提下,会尽力减少临时对象的产生。
1 #include <stdio.h>
2
3 class Test
4 {
5 int mi;
6 public:
7 Test(int i)
8 {
9 printf("Test(int i) : %d
", i);
10 mi = i;
11 }
12 Test(const Test& t)
13 {
14 printf("Test(const Test& t) : %d
", t.mi);
15 mi = t.mi;
16 }
17 Test()
18 {
19 printf("Test()
");
20 mi = 0;
21 }
22 int print()
23 {
24 printf("mi = %d
", mi);
25 }
26 ~Test()
27 {
28 printf("~Test()
");
29 }
30 };
31
37 int main()
38 {
39 Test t(10); //等价于Test t = Test(10)-------->可以这样来解读,a.生成一个临时对象,b. 用临时对象初始化t对象。既然是这样就会涉及调用拷贝构造函数。
//但是从编译运行结果来看,根本就没有去调用拷贝构造函数。说明编译器根本没有按照上面分析的这种思路去走,为什么呢?难道是之前我们分析的有问题?
//因为现代的c++编译器都会减少临时对象的产生。c++编译器为了杜绝临时对象的产生,直接将Test t = Test(10)等价为了Test t = 10;
t.print();
45 return 0;
46 }
#include <stdio.h>
class Test
{
int mi;
public:
Test(int i)
{
printf("Test(int i) : %d
", i);
mi = i;
}
Test(const Test& t)
{
printf("Test(const Test& t) : %d
", t.mi);
mi = t.mi;
}
Test()
{
printf("Test()
");
mi = 0;
}
int print()
{
printf("mi = %d
", mi);
}
~Test()
{
printf("~Test()
");
}
};
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;
}
小结:
直接调用构造函数将产生一个临时对象
临时对象是性能的瓶颈,也是bug的来源之一
现代C++编译器会尽力避开临时对象(前提是不影响运行结果)
实际工程开发中需要人为的避开临时对象。
注意:从这节课的代码来看,逻辑没有什么问题,但是就是因为临时对象的存在,使程序的执行结果产生不可思议的问题。此时就要考虑临时对象了。