1、傳值參數(pass-by-value parameter)
當進行這樣一個調用時:
s將被拷貝構造一個備份(臨時對象),這個備份參與函數體內的運算,原件不會被改動。
2、返回值(return value)
當s被返回時,會被拷貝構造一個備份(臨時對象),這個變量被返回,函數體內的局部變量被銷燬。
3、隱式類型轉換(Implicitly Typecast)
當實行一個賦值動作:
時,因爲String:perator=並沒有針對char *和const char *的重載,而最可能的做法就是將const char *隱式轉換爲一個String對象。在這個過程中就會產生一個String類的臨時對象。
4、++ 和 --
因爲iter++的返回值需要返回原值,所以這個值在增1之前必須保留,這個保留就必須使用一個臨時對象。這相當於是返回值的一個典型情況。
5、值存儲的STL容器
主要針對std::vector,因爲vector具有自動擴充容量的功能,在每次擴充容量的時候,大部分實現的算法是new一個新的內存塊,將原有的每個元素拷貝複製到新的內存,然後刪除原有的內存塊。這期間要使用大量的臨時對象。
二、臨時對象的弊端
臨時對象實際上就是通過拷貝構造函數產生的,沒有變量名的const對象,所以每產生這樣一個對象,都會調用一次拷貝構造函數。如果這個類很“大”,那麼會佔用相當的資源和時間,從而降低效率。特別是在第5種情形中,會產生大量的臨時對象,情況尤其嚴重。
三、臨時對象的應對之道
解決臨時對象的主要方法就是使用指針和引用,減少構造函數調用的機會。
1、避免使用傳值參數
實際上絕大部分的傳值參數都可以使用常量引用(const &)來代替,而常量引用的參數不會產生臨時對象。
這樣的寫法可以有效的避免在參數傳遞過程中產生臨時對象。
但是,引用通常是由指針實現的,對於基本類型來說,指針操作比直接操作要慢一些,並且增加了操作的複雜性。而基本類型的操作都不復雜,而且很快,所以,對於基本類型來說,沒有必要使用常量引用手法。
2、隱式類型轉換
隱式類型轉換的問題是定義了針對某個類型的構造函數,但是沒有定義針對該類型的operator=所導致的,所以,構造函數和operator=必須成對出現。
3、++ 和 --
這個問題已經討論過很多了,
因此,在可能的情況下,使用++i代替i++,就可以避免出現臨時對象。
4、在STL容器(尤其是vector)中使用對象指針而不是對象實體作爲元素
vector因爲有重新分配內存的動作,在這個動作中會產生大量的臨時對象,極大的降低效率。解決方法就是使用對象指針作爲vector的元素,避免採用對象實體。另外,用reserve事先分配內存也是避免內存交換的方法。
但是要注意,STL使用的是值(value)語意,所以對容器中指針的管理要自己負責,比如容器析構和copy時,可能要自己多做一些工作,以保證數據的正確性。
更好的方法當然是對指針語意的vector做一個封裝。
5、函數的返回值
函數的返回值不能以引用或指針的方式返回函數內部的局部變量,如果需要返回局部變量的值,必須以Object形式返回。在這種情況下,臨時對象是不可避免的。這個問題上,《Effective C++》Item 23 給出了更詳細的討論。
對於無法避免的臨時對象,我們至少可以將“危害”降到最低點。
利用編譯器優化
a) 上面提到,函數必須以值的形式返回局部變量。由於這種形式比較普遍並且無法避免,現代的編譯器大部分都可以實行一個叫做 Named Return Value(NRV)的優化。這個優化可以避免不必要的臨時對象的產生。在編譯器實施NRV優化時,如下的代碼:
將被優化爲類似這樣:
很明顯的,這個優化減少了一個臨時對象的產生。
NRV優化由編譯器自動實施,並沒有什麼編譯選項,也無法人工干預。所以,不能依賴於該優化,不過該優化在某些情況下的確可以提高效率。
對NRV的討論,可以參考《深度探索C++對象模型》一書的“在編譯器層面做優化”一章。
b) 目前幾乎所有的編譯器都可以保證
這樣的形式將不會產生臨時對象。
6、其它
儘可能少的調用
這個問題最典型的現象就是循環的終止條件。
在這裏,每循環一次,iter != str_vector.end() 這個判斷就要進行一次,因爲str_vector.end()是一個函數調用,因爲它的返回值是一個iterator的Object,所以每次調用都會產生一個臨時對象。如果寫成這樣,臨時對象就會只被生成一次:
四、其它問題
臨時對象的生命週期
臨時對象是不可見的,但是,在特定的情況下,它的生存週期問題可能導致問題。對這個問題,標準規格上的定義是:
臨時對象的被摧毀,應該是對完整表達式求值過程中的最後一個步驟。該完整表達式造成臨時對象的產生