1. 程式人生 > >條款6:GotW#2 臨時物件(Temporary Objects)

條款6:GotW#2 臨時物件(Temporary Objects)

試想你正在閱讀另一個程式設計師寫好的函式程式碼,而這個函式中卻有至少三個地方用到了不必要的臨時物件,你能發現幾個,程式設計師又該如何修改?

string FindAddr(list<Employee> l,string name)
{
	for(list<Employee>::iterator i = l.begin();
		i != l.end(); i++)
	{
		if(*i == name)
		{
			return (*i).addr;
		}
	}
	return " ";
}

【解答】

兩個明顯的臨時物件隱藏在函式宣告中:

string FindAddr(list<Employee> l,string name)

1和2兩處:兩個引數都應該使用常量應用(const  reference),使用傳值方式將導致函式對list和string拷貝,其效能程式碼是高昂的。

【規則】:請使用const&而不是傳值拷貝。

for(list<Employee>::iterator i = l.begin();
		i != l.end(); i++)

第3處:先增(preincrement)比後增(postincrement)操作效率高,因為進行後增操作時,物件不但必須自己遞增,而且還要返回一個包含遞增之前的值的臨時物件,內建型別int這樣都如此。

【學習指導】:請使用先增(preincrement)操作,避免使用後增(postincrement)操作。

if(*i == name)

第4處:這裡沒有體現Employee類,如果想讓它行得通,則要麼能轉換成一個string物件,要麼通過一個轉換建構函式來得到一個string。然而這兩種方法都會產生臨時物件,從而導致string或者Employee的operator==呼叫。

【學習指導】:時刻注意因為引數轉換操作而產生的臨時物件。一個避免它的好辦法就是儘可能顯示(explicit)的使用建構函式(constructor)。

return " ";

第5處:這裡產生一個臨時的(空的)string物件。更好的做法是,宣告一個string物件來儲存返回值,然後用一個單獨的return語句返回這個string。這樣使得編譯器在某些情況下啟用“返回值最優化”處理來省略掉臨時物件。

【規則】:請遵循使用所謂的“單入口/單出口”(single-entry/single-exit)規則。絕不要在一個函式裡寫多個return語句。

【作者記:當進行了進一步效能測試之後,我不再認同上面這條建議。我已經在《Exceptional C++》中修改了這一點。】

string FindAddr(list<Employee> l,string name)

第*點:這可是一處遮眼法(red herring)。看上去,好像你可以很簡單的把返回值宣告成string&而不是string,來避免不不要的臨時物件。這樣的做法是錯誤的,如果你的程式只是在程式碼試圖使用引用的時候崩潰(因為那個所指的區域性物件不存在了),那你就夠幸運,如果你不走運,你的程式碼看似能夠正常工作,卻時不時冷不防失敗幾次,從而使你不得不在除錯程式中度過。

【規則】:絕對絕對不要返回一個區域性物件的引用(reference)。

【作者記:有一些帖子正確的指出,你可以宣告一個遇到錯誤才返回的靜態物件,從而實現不改變函式語意的情況下返回一個引用。同時意味著,在返回引用的時候,你必須注意物件的生存週期。】

其實還有很多地方可以優化:諸如“避免對end()進行多餘的呼叫”等等。程式設計師可以(也應該)使用const_iterator。可以暫時拋開不談,我們仍可以得到如下的正確程式碼:

string FindAddr(const list<Employee>& l ,const string& name)
{
	string addr;
	for(list<Employee>::const_iterator i = l.begin(),
		i != l.end(); ++i)
	{
		if((*i).name == name)
		{
			addr = (*i).addr;
			break;
		}
	}
	return addr;
}