標籤:

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
}

上面這個函數,不論傳遞進來的實參是左值還是右值,結果都是調用了一次copy構造函數

而如果我們這樣寫:

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{};

這時調用的a.f(T t);可以看作a.f(int t);

這時形參接受右值,如,立即數,std:move處理過的值

2.模版類型未確定,如直接調用template & f(T t);

由於這個函數不是模版類的成員函數,那麼在調用這個函數前我們就無法確定T的類型。這時形參被稱為universe parameter 即他能接受各種類型,如左值引用,const 左值引用,右值引用以及const 右值引用


建議研究一下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。


推薦閱讀:

鼎叔的編程慢習慣
鵬哥帶你學編程-引子
如何學習編譯原理中的純理論?

TAG:編程 | C | C11 |