第23课 神秘的临时对象

神秘的临时对象(上)

有趣的问题:

下面的程序输出什么,为什么?

 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\n", 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\n", 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\n", i);
10         mi = i;
11     }
12     Test(const Test& t)
13     {
14         printf("Test(const Test& t) : %d\n", t.mi);
15         mi = t.mi;
16     }
17     Test()
18     {
19         printf("Test()\n");
20         mi = 0;
21     }
22     int print()
23     {
24         printf("mi = %d\n", mi);
25     }
26     ~Test()
27     {
28         printf("~Test()\n");
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\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;
}

小结:

直接调用构造函数将产生一个临时对象
临时对象是性能的瓶颈也是bug的来源之一
现代C++编译器会尽力避开临时对象(前提是不影响运行结果)
实际工程开发中需要人为的避开临时对象。

注意:从这节课的代码来看,逻辑没有什么问题,但是就是因为临时对象的存在,使程序的执行结果产生不可思议的问题。此时就要考虑临时对象了。



posted @ 2019-11-13 23:30  一代枭雄  阅读(185)  评论(0编辑  收藏  举报