C++中的臨時對象


1,臨時對象神秘在於不知不覺就請入程序當中,並且給程序帶來了一定的問題;

 

2,下面的程序輸出什么?為什么?

 1 #include <stdio.h>
 2 
 3 class Test
 4 {
 5     int mi;
 6 public:
 7     Test(int i)
 8     {
 9         mi = i;
10     }
11     
12     Test()  // 這里程序作者想要代碼復用,直接調用已經構造好的函數來完成沒有參數的構造函數的函數體;
13     {
14         Test(0);  // 得到臨時對象,沒有名字,就意味着作用域只在這個語句,過了這個語句,就沒法被訪問到了;這里的語句在這里沒有什么作用,等價於空的語句;
15     }
16     
17     void print()
18     {
19         printf("mi = %d\n", mi);
20     }
21 };
22 
23 int main()
24 {
25     Test t;
26     
27     t.print();  // 期望 mi 為 0;但是結果卻是隨機值;
28 
29     return 0;
30 }

 

3,程序意圖:

        1,在 Test() 中以 0 作為參數調用 Test(int i);

        2,將成員變量 mi 的初始值設置為 0;

   運行結果:

       1,成員變量 mi 的值為隨機值;

  

4,構造函數是一個特殊的函數:  

    1,是否可以直接調用?

       1,給編譯器主動調用的,但也可直接手工調用;

    2,是否可以在構造函數中調用構造函數?

       1,從編譯器的編譯結果來看在語法上合法;

    3,直接調用構造函數的行為是什么?

       1,直接調用構造函數將會產生一個臨時對象;

           1,是一個合法的 C++ 對象,生命期只有一條語句時間;

           2,過了這個語句臨時對象就會被自動析構而不復存在;

           3,臨時對象是沒有名字的;

       2,臨時對象的生命周期只有一條語句;

       3,臨時對象的作用域只在一條語句中;

       4,臨時對象是 C++ 中值得警惕的灰色地帶;

           1,同 C 中的野指針一樣必須警惕;

          

5,避免因代碼復用調用構造函數而帶來的臨時對象的解決方案:

 1 #include <stdio.h>
 2 
 3 class Test {
 4     int mi;
 5     
 6     void init(int i)
 7     {
 8         mi = i;
 9     }
10     
11 public:
12     Test(int i) 
13     {
14         init(i);  
15     }
16     
17     Test() 
18     {
19         init(0);  // 工程中代碼復用方式
20     }
21     
22     void print() {
23         printf("mi = %d\n", mi);
24     }
25 };
26 
27 
28 int main()
29 {
30     Test t;
31     
32     t.print();
33 
34     return 0;
35 }

 

6,臨時對象的測試:

 1 #include <stdio.h>
 2 
 3 class Test {
 4     int mi;
 5     
 6     void init(int i)
 7     {
 8         mi = i;
 9     }
10     
11 public:
12     Test(int i) 
13     {
14         printf("Test(int i)\n");
15         init(i);  
16     }
17     
18     Test() 
19     {
20         printf("Test()\n");
21         init(0);  
22     }
23     
24     void print() {
25         printf("mi = %d\n", mi);
26     }
27     
28     ~Test()
29     {
30         printf("~Test()\n");
31     }
32 };
33 
34 
35 int main()
36 {
37     printf("main begin1\n");
38     
39     Test();  // 產生臨時對象;打印 ==> Test() ~Test()
40     
41     Test(10);  // 產生臨時對象;打印 ==> Test(int i) ~Test()
42     
43     printf("main end1\n");
44     
45     printf("main begin2\n");
46     
47     Test().print();  // 臨時對象生成之后直接調用 print() 函數;這里可以通過編譯,因為調用了構造函數之后呢就會產生臨時對象了,通過合法的 C++ 對象加上點操作符來調用對應的成員函數是完全沒有問題,完全合法的;產生臨時對象;打印 ==> Test()    mi = 0    ~Test()
48     Test(10).print(); //產生臨時對象;打印 ==> Test(int i) mi = 10 ~Test() 
49     
50     printf("main end2\n");
51 
52     return 0;
53 }

    1,這里僅是教育需要,向大家介紹這個知識點,在以后工程開發中,萬不可這樣寫代碼,一定要去避免臨時對象的產生和使用;

      

7,現代 C++ 編譯器在不影響最終執行結果的前提下,會盡力減少臨時對象的產生;

 

8,神秘的臨時對象編程實驗:

    1,證明 C++ 編譯器在極力的減少臨時對象的產生;

 1 #include <stdio.h>
 2 
 3 class Test
 4 {
 5     int mi;
 6     
 7 public:
 8     Test(int i)
 9     {
10         printf("Test(int i) : %d\n", i);
11         mi = i;
12     }
13     
14     Test(const Test& t)
15     {
16         printf("Test(const Test& t) : %d\n", t.mi);
17         mi = t.mi;
18     }
19     
20     Test()
21     {
22         printf("Test()\n");
23         mi = 0;
24     }
25     
26     int print()
27     {
28         printf("mi = %d\n", mi);
29     }
30     
31     ~Test()
32     {
33         printf("~Test()\n");
34     }
35 };
36 
37 Test func()
38 {
39     return Test(20);  // 生成一個臨時對象,函數調用結束后就立即銷毀臨時對象了
40 }
41 
42 int main()
43 {
44 /*
45     Test t(10); // 等價於 Test t = Test(10); 於是可解讀為:1,生成臨時對象 2,用臨時對象初始化即將生成的 t 對象,於是必然涉及到調用拷貝構造函數,但是編譯器打印的結果為 Test(int i) : 10    mi = 10   ~Test()根本沒有任何拷貝構造函數的跡象產生;編譯器根本沒有像我們解讀的上述的兩個步驟執行;這是因為現代的 C++ 編譯器都會盡量的減少臨時對象的產生;從執行結果來看,上面的分析是沒有任何錯誤的,只是上面的還要再調用一次拷貝構造函數,通過上面的分析,即便結果沒有任何的變化,但是效率降低了,因此在這個地方 C++ 編譯器為了杜絕臨時對象的產生,直接將Test t = Test(10) 等價成為了 Test t = 10,這樣就杜絕了臨時對象的產生;杜絕臨時對象的產生是因為其往往會帶來性能上面的題,先生成一個臨時對象調用了一次構造函數,再將臨時對象通過拷貝構造函數來初始化 t,也就是說調用了兩次構造函數,如果說我們極力的減少臨時對象的產生,那么通過上述第二個方式等價 ,這樣調用一次構造函數就可以了,少調用了一次拷貝構造函數,性能就提升了;在這個地方從性能上我們沒有多大體會,但在實際的工程 開發中,構造函數里面所做的初始化工作往往是紛繁復雜的,比方說有些類的對象在初始化的時候,在調用構造函數的時候很可能打開外部設備,對設備進行初始設置等等,如果這個時候多了一次構造函數的調用,時間就消耗了;為什么要這樣呢?因為 C++ 天生繼承了 C 的特性,C 的一個最大特性就是高效,所以 C++ 的一個特性也是要極力的做到高效,該省掉臨時對象的地方,就該省掉;               
46 */    
47     Test t = Test(10); // ==> Test t = 10;
48     
49     t.print();
50     
51     Test tt = func();  // ==> Test tt = Test(20); ==> Test tt = 20; fun() 返回一個臨時對象,到達了賦值符號右側,此時臨時對象里面的值會初始化 t 對象;當代的 C++ 編譯器,在不影響最終結果的情況下,會極力的減少臨時對象的產生;
52                    
53     tt.print();  // 打印 mi = 20;
54     
55     return 0;
56 }

 

9,小結:

    1,直接調用構造函數將產生一個臨時對象;

    2,臨時對象是性能的瓶頸,也是 bug 的來源之一;;

       1,C++ 中除了野指針,還有臨時對象;

       2,工作中,如果出現了奇怪的問題,要從這兩個方面考慮;

    3,現代 C++ 編譯器會盡力避開臨時對象;

       1,避開的前提是不影響最終的執行結果;

    4,實際工程開發中需要人為的避開臨時對象;

       1,避免調用構造函數來初始化;

       2,設計函數不要引入像 fun() 函數中的臨時對象;


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM