條款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;
}