從一個示例反思如何避免c++中的臨時對象

說明


臨時對象是隱晦的,但對性能的影響是不可忽視的。

本文通過剖析示例,找出臨時對象的藏身之所,曝光並消滅它。

示例


代碼片斷如下:

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++》

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章