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() 函數中的臨時對象;