書歸正傳,我們知道在C++的建立物件是一個費時,費空間的一個操作。有些固然是必不可少,但還有一些物件卻在我們不知道的情況下被建立了。通常以下三種情況會產生臨時物件:
1,以值的方式給函式傳參;
2,型別轉換;
3,函式需要返回一個物件時;
現在我們依次看這三種情況:
一,以值的方式給函式傳參。
我們知道給函式傳參有兩種方式。1,按值傳遞;2,按引用傳遞。按值傳遞時,首先將需要傳給函式的引數,呼叫拷貝建構函式建立一個副本,所有在函式裡的操作都是針對這個副本的,也正是因為這個原因,在函式體裡對該副本進行任何操作,都不會影響原引數。我們看以下例子:
/*----------------------- Platform.:WinXp + VC6 -----------------------*/ #include <stdio.h> class CTemp { public: int a; int b; public: CTemp(CTemp& t){ printf("Copy function!\n");a = t.a;b = t.b;}; CTemp(int m = 0,int n = 0); virtual ~CTemp(){}; public: int GetSum(CTemp ts); }; CTemp::CTemp(int m , int n) { printf("Construct function!\n"); a = m;b=n; printf("a = %d\n",a); printf("b = %d\n",b); } int CTemp::GetSum(CTemp ts) { int tmp = ts.a + ts.b; ts.a = 1000; //此時修改的是tm的一個副本 return tmp; } //--------------Main函式----------------- void main() { CTemp tm(10,20); printf("Sum = %d \n",tm.GetSum(tm)); printf("tm.a = %d \n",tm.a); } |
--------------------------------------------------------
Output:
Construct function!
a = 10
b = 20
Copy function!
Sum = 30
tm.a = 10
------------------------------------------------------
我們看到有呼叫了拷貝建構函式,這是tm在傳給GetSum做引數時:
1,呼叫拷貝建構函式來建立一個副本為GetSum函式體內所用。
2,在GetSum函式體內對tm副本進行的修改並沒有影響到tm本身。
解決辦法:
針對第一種情況的解決辦法是傳入物件引用(記住:引用只是原物件的一個別名(Alias)),我們將GetSum程式碼修改如下:
int CTemp::GetSum(CTemp& ts) { int tmp = ts.a + ts.b; ts.a = 1000; //此時通過ts這個引用參考(refer to)物件本身 return tmp; } |
----------------------------------------------------------
Output:
Construct function!
a = 10
b = 20
Sum = 30
tm.a = 1000
--------------------------------------------------------
可以通過輸出看本,通過傳遞常量引用,減少了一次臨時物件的建立。這個改動也許很小,但對多繼承的物件來說在構建時要遞迴呼叫所有基類的建構函式,這對於效能來說是個很大的消耗,而且這種消耗通常來說是沒有必要的。
二,型別轉換生成的臨時物件。
我們在做型別轉換時,轉換後的物件通常是一個臨時物件。編譯器為了通過編譯會建立一起我們不易察覺的臨時物件。再次修改如上main程式碼:
void main() { CTemp tm(10,20),sum; sum = 1000; //呼叫CTemp(int m = 0,int n = 0)建構函式 printf("Sum = %d \n",tm.GetSum(sum)); } |
-----------------------------------------------------------
Output:
Construct function!
a = 10
b = 20
Construct function!
a = 0
b = 0
Construct function!
a = 1000
b = 0
Sum = 1000
----------------------------------------------------------
main函式建立了兩個物件,但輸出卻呼叫了三次建構函式,這是為什麼呢?
關鍵在 sum = 1000;這段程式碼。本身1000和sum型別不符,但編譯器為了通過編譯以1000為參呼叫建構函式建立了一下臨時物件。
解決辦法:
我們對main函式中的程式碼稍作修改,將sum申明推遲到“=”號之前:
void main() { CTemp tm(10,20); CTemp sum = 1000; printf("Sum = %d \n",tm.GetSum(sum)); } |
----------------------------------------------------------
Output:
Construct function!
a = 10
b = 20
Construct function!
a = 1000
b = 0
Sum = 1000
----------------------------------------------------------
只作了稍稍改動,就減少了一次臨時物件的建立。
1,此時的“=”號由原本的賦值變為了構造。
2,對Sum的構造推遲了。當我們定義CTmep sum時,在main的棧中為sum物件建立了一個預留的空間。而我們用1000呼叫構造時,此時的構造是在為sum預留的空間中進行的。因此也減少了一次臨時物件的建立。
三,函式返回一個物件。
當函式需要返回一個物件,他會在棧中建立一個臨時物件,儲存函式的返回值。看以下程式碼:
#include <stdio.h> class CTemp { public: int a; public: CTemp(CTemp& t) //Copy Ctor! { printf("Copy Ctor!\n"); a = t.a; }; CTemp& operator=(CTemp& t) //Assignment Copy Ctor! { printf("Assignment Copy Ctor!\n"); a = t.a; return *this; } CTemp(int m = 0); virtual ~CTemp(){}; }; CTemp::CTemp(int m) //Copy Ctor! { printf("Construct function!\n"); a = m; printf("a = %d\n",a); } CTemp Double(CTemp& ts) { CTemp tmp; //構建一個臨時物件 tmp.a = ts.a*2; return tmp; } //-------------Main函式----------------- void main() { CTemp tm(10),sum; printf("\n\n");
sum = Double(tm);
printf("\n\nsum.a = %d \n",sum.a); }
|
---------------------------------------------------------
Output:
Construct function!
a = 10
Construct function!
a = 0
Construct function!
a = 0
Copy Ctor!
Assignment Copy Ctor!
sum.a = 20
--------------------------------------------------------
我特地加寬了語句:
sum = Double(tm);
這條語句竟生成了兩個物件,Horrible! 我們現在將這條語句逐步分解一下:
1,我們顯式建立一個tmp臨時物件,
語句:CTemp tmp;
2,將temp物件返回,返回過程中呼叫Copy cotr建立一個返回物件,
語句:return tmp;
3,將返回結果通過呼叫賦值拷貝函式,賦給sum
語句: sum = 函式返回值;(該步並沒有建立物件,只是給sum賦值)
tm.Double返回一個用拷貝建構函式生成的臨時物件,並用該臨時物件給sum賦值.
上面的第1步建立物件可以不用建立,我們可以直接對返回值進行操作,有些C++編譯器中會有一種優化,叫做(NRV,named return value).不過本人使用的VC++6.0並沒有這個啟用這個優化。
第2步建立的返回物件是難以避免的,你或許想可以返回一個引用,但你別忘記了在函式裡建立的區域性物件,在返回時就被銷燬了。這時若再引用該物件會產生未預期的行為。(C#中解決了這個問題)。
解決方法:
我們將物件直接操作(Manipulate)返回物件,再結合上面的減少臨時物件的方法,將函式Double的程式碼,及main函式中的程式碼修改如下:
CTemp Double(CTemp& ts) { return ts.a*2; } /*--------上面的程式碼相當於------- CTemp _ret void Double(CTemp& ts) { _ret.a = ts.a*2; } ---------------*/
//---------Main函式----------- void main() { CTemp tm(10); printf("\n\n");
CTemp sum = Double(tm); printf("\n\nsum.a = %d \n",sum.a); }
|
--------------------------------------------------------
Output:
Construct function!
a = 10
Construct function!
a = 20
sum.a = 20
-------------------------------------------------------
發現減少了一次建構函式呼叫(tmp),一次拷貝建構函式(tmp拷貝給返回物件)呼叫和一次賦值拷貝函式呼叫.(Assignment Copy Ctor),這是因為:
返回物件直接使用為sum預留的空間,所以減少了返回臨時物件的生成——返回物件即是sum,返回物件的建立即是sum物件的建立.多麼精妙!
來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/10697500/viewspace-660896/,如需轉載,請註明出處,否則將追究法律責任。