我們知道在C++的創建對象是一個費時,費空間的一個操作。有些固然是必不可少,但還有一些對象卻在我們不知道的情況下被創建了。通常以下三種情況會產生臨時對象:
1,以值的方式給函數傳參;
2,類型轉換;
3,函數需要返回一個對象時;
現在我們依次看這三種情況:
一,以值的方式給函數傳參。
我們知道給函數傳參有兩種方式。1,按值傳遞;2,按引用傳遞。按值傳遞時,首先將需要傳給函數的參數,調用拷貝構造函數創建一個副本,所有在函數裏的操作都是針對這個副本的,也正是因爲這個原因,在函數體裏對該副本進行任何操作,都不會影響原參數。我們看以下例子:
#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;
}
//---------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對象的創建.多麼精妙!