C++的函數形參什麼時候應該使用右值引用?
在C++11之前,函數的形式參數選取有很簡單的指導規則。假設T是變數類型,那麼
T:直接傳值,適用於複製代價較小的變數類型,且無需修改T const:左值引用,無需修改,適用於複製代價較大的變數類型T:左值引用,需要修改C++11中引入了右值引用的概念,那麼現在問題就來了:函數的形式參數什麼時候應該是 T ?
當你需要在函數內copy參數 並且 要將copy的結果保存在非該函數的棧內 時。
這兩個條件必須同時都滿足。最典型的例子,就是STL的那些支持add操作的容器:因為在你給一個容器add元素時,你第一copy了外面傳遞進來的元素第二將這個元素存儲在了容器里。比如vector的push_back就有兩個版本:一個push_back的參數類型是const ,另一個是。這麼做的好處就是如果傳遞進來的是一個右值,那麼此時在push_back里就只需要move而不需要copy。比如,vec.push_back(MyClass()); // 此時參數為右值,調用第二個版本的push_back========================== BONUS =========================如果只滿足第一個條件而不滿足第二個條件,沒有右值引用時我們是這樣寫的:
void func1(const T t)
{
T local = t;
// use local
}
void func2(T t)
{
// use t
}
此時如果傳遞進來左值,那麼依然調用了一次copy構造函數;而如果傳遞進來了右值,則調用的是move構造函數。
那麼問題來了:在只滿足第一個條件的前提下,我們是不是應該徹底放棄const T 而全部使用值傳遞呢?這個問題等效於:是不是move一定比copy好所以能move就應該盡量move?
答案是:NO。現在很多「大牛」喜歡這麼寫:MyClass::Myclass(std::string str) : str_member(std::move(str)){}
在上面這行代碼中,如果實參是左值,則調用一次copy一次move;如果實參是右值,則調用兩次move。
而如果我們依然用以前的const std::string str做參數,則不論實參是左值還是右值,均調用一次copy。
很顯然,「大牛」默認了一個事實:move的代價很低、低到多做一次move沒什麼關係(實參為左值時);而copy代價非常高,高到用兩次move換一次copy都是很值得(實參為右值時)。因此,如果你的參數類型的確符合上面的事實,那麼按照「大牛"的做法就是沒問題的;但是並非所有的參數類型都符合上述事實。以std::string為例:
因為當你傳遞std::string為參數且這個字元串的長度並不十分長時,如果用func1的方法,編譯器是可以做一些優化工作的(SSO: small string optimization 這種優化是基於string內部的char *);而如果你用func2,SSO就不存在了而SSO要比「把copy改成move所帶來的優化」更優。(Effective Modern C++)退一步講,即使沒有「短小的std::string」的優化問題,對於那些體量小的、不存在需要考慮深拷貝和淺拷貝差別的、甚至只會使用trivial構造的類,我們依然可以使用func1的方式,就算你換用func2,對效率的提升是微乎其微甚至是不存在的。
而如果連第一個條件都不滿足,怎麼辦?
很顯然,答案是:以前怎樣現在還怎樣。如果你並不在函數內copy參數,那麼const T 顯然是最好的方法。
註:本答案不涉及模板。在C++11的新標準下,的引入是為了移動操作。
我們可以分別重載和形參的函數,這樣的意義是讓一個函數同時具有複製和移動兩種傳參方式。簡單的來說,有兩種傳入形參的方式: (1)const T 傳入左值引用用於複製,這時原則上不許對T修改,所以應該加上const(2) T 傳入右值引用用於移動,由於修改右值不會對其他變數進行修改,這樣我們就可以實現諸如 SWAP(a,t.a)的 操作,而不用去申請新的內存空間在模版中考慮兩種情況1.模版類型T已確定,如vector &
這時調用的a.f(T t);可以看作a.f(int t);
這時形參接受右值,如,立即數,std:move處理過的值2.模版類型未確定,如直接調用template &建議研究一下Effective Modern C++,Effective Modern C++ (豆瓣) 裡邊對T這種形式(不只是右值引用,在引用摺疊規則下還可以接受左值引用)的詳細解釋。
沒人說template么?完美轉發啊~
適用於類成員變數的setter
舉例
Class foo{Void set_name(std::string name)
{ _name = name; }}調用端Std::string name = ......;Foo.set_name(std::move(name));//name cannot be used any more在這種情況下可以節省一次拷貝粗見: 你覺得你傳入的對象只會被某個函數所使用。比如你new個對象作為某函數的傳入參數,又只在某函數中使用。就沒有必要傳入複製的值或者左值。
右值通常指表達式或語句中產生的臨時變數,一個函數的返回值就是一個臨時變數。當一個變數的複製成本較高時(高低是相對的,取決於上下文對效率的要求),你可能需要特殊指針,才能減少一次複製,譬如,auto_ptr。
右值引用提供了你再次修改右值的機會,譬如,和它進行swap。
一個函數返回局部變數,想要減少一次複製的話,使用右值引用是個更簡潔的方法。
不僅僅是函數,因為允許運算符重載,你看到的運算符背後很可能是函數實現的。在不降低代碼可讀性的同時,儘可能提高了運行效率。
和模板結合,它還能大幅度的減少冗餘代碼,進行類型的精確傳遞。找到c++ primer 第五版的拷貝控制這一章,在移動構造那塊知識里有說。
本末倒置啊,一個指針就能解決的問題,結果引入這麼多概念,結果比指針更加容易出錯,更加難懂。
C++11里用右值引用傳遞參數就是用來取代C++98里的值傳遞這種方式。
如果參數是右值引用,那麼它可以接收任何形式的參數,並且不會有複製的代價。
就是說,用void f(T)替代void f(T)。
代價是,如果該函數如果要把參數傳遞給另一個參數是右值引用的函數,需要用std::forward。
推薦閱讀: