說明
臨時對象是隱晦的,但對性能的影響是不可忽視的。
本文通過剖析示例,找出臨時對象的藏身之所,曝光並消滅它。
示例
代碼片斷如下:
string FindAddr(list<Employee> l, string name)
{
for (auto i = l.begin(); i != l.end(); i++)
{
if (*i == name)
{
return (*i).addr;
}
}
return "";
}
這段代碼簡單到不需要任何的註釋,且可能存在於任何規模的項目中。但,這裏面有多少處臨時對象?
剖析
先說下答案吧,至少有 5處:
- 函數參數
list<Employee> l, string name
:使用傳值的方式,性能代價高昂。應該使用引用。 - 循環體中的i++,後置操作符時,對象不但自己遞增,還要返回一個包含遞增前值的臨時對象。內建int i++也是如此。應該使用前置操作符。
- 條件判斷語句:
*i == name
,隱含一個隱式轉換,從Employee到string,要麼通過構造函數,要麼操作符,這兩種方式都會產生臨時對象,可以使用顯式聲明(explicit)避免。 - 返回語句:
return "";
,會產生一個臨時的string對象。可以先定義一個局部string來儲存返回值,再使用return返回,利用返回值優化。 - 函數返回類型:string。這是個陷阱,如果把返回類型修改爲引用(string&),那麼再返回局部變量就會導致對局部對象的引用,造成未定義的行爲。
好了,看到了這些問題,也知道了正確的做法,修改版本如下:
string FindAddr(list<Employee>& l, string& name)
{
string addr;
for (auto i = l.begin(); i != l.end(); ++i)
{
if ((*i).name == name)
{
addr = (*i).addr;
break;
}
}
return addr; // 利用了RVO(返回值優化,函數的返回類型與局部變量的類型完全一致時會啓用)
}
程序沒問題了,但對於標準容器使用遍歷式的循環,還是有點不順眼。
使用標準庫
讀過《Effective STL》的小夥伴們,是不是也有種對手寫循環的反感?那就對了,因爲可以使用算法!
使用標準庫的算法的好處就不多說了,直接上代碼:
string FindAddr(list<Employee>& l, string& name)
{
string addr;
auto i = find(l.begin(), l.end(), name);
if (i != l.end())
{
addr = (*i).addr;
}
return addr; // 利用了RVO(返回值優化,函數的返回類型與局部變量的類型完全一致時會啓用)
}
一句話:利用標準庫的算法,不要重複造輪子。這樣更好、更快、更強!
小結
簡單的代碼中隱藏着這麼多的臨時對象,它們看不到,但我們必須要知道。
這對於性能提升來說是必要的,因爲只要簡單地改動就能達到。
注重效率,從寫代碼開始。
參考資料
《Exceptional C++》