右值引用

右值引用。背景.在C++11提出右值引用之前，C++03及更早的C++標準中，表示式的“值分類”獲得對應的記憶體地址。C++03及以前的標準定義了在表示式中左值到右值的三類隱式自動轉換：作為一種追求執行效率的語言，C++在用臨時物件或函式返回值給左值物件賦值時的深度複製給被賦值的左值物件，效率改善將是顯著的。這就是移動語義的與傳統的複製賦值運算子成員函式的實現機制。可以透過函式過載來確定是呼叫複製語義還是移動語義的實現。右值引用就是為了實現移動語義與完美轉發所需要而設計出來的新的資料型別。右值引用的例項對應於臨時物件；右值引用並區別於左值引用，用作形參時能過載辨識是呼叫複製語義還是移動語義的函式。無論是傳統的左值引用還是C++11引進的右值引用，從編譯後的反彙編層面上，都是物件的儲存地址與自動解-{}-引用。C++語言在引入了右值引用之後，面臨著一個問題：如何讓程式設計者指出哪個物件具有臨終值？這有兩種顯式指定方法：如果函式的返回型別為右值引用，或者透過型別轉換如static_cast或者std::move()模板函式。定義.設X為任何一種資料型別，則定義X&amp;&amp;是到資料型別X的右值引用。例如：int i;int &amp;j=i; //傳統的左值引用資料型別的變數並初始化int &amp;&amp;k=std::move(i); //定義一個右值引用資料型別的變數並初始化。std::move定義於語義.右值引用是一種資料型別，既有右值性質，也有左值性質。在C++11中為右值引用專門定義了臨終值這一概念。右值引用很類似於傳統的左值引用。例如：int s=101;int&amp;&amp; foo(){return static_cast(s);} //返回值為右值引用int main() {int i=foo(); //右值引用作為右值，在賦值運算子的右側int&amp;&amp; j=foo(); //j是具名引用。因此運算子左側的右值引用作為左值int* p=&amp;j; //取得j的記憶體地址C++中，引用是指繫結到記憶體中的相應物件上。左值引用是繫結到左值物件上；右值引用是繫結到臨時物件上。這裡的左值物件是指可以透過取地址&amp;運算子得到該物件的記憶體地址；而臨時物件是不能用取地址&amp;運算子獲取到物件的記憶體地址。C++11有如下引用繫結規則：對上述引用繫結規則，值得注意的是：由於右值引用主要針對移動語義用來修改被引用的物件的內容，所以常量右值引用較少用到。函式返回值是右值資料型別還是右值引用型別，區別在於前者是“傳值”，後者是“傳引用”可以修改被引用的物件。例如：int i = 101, j = 101;int main()set(foo());std::cout « i « std::endl;set(bar());std::cout « i « std::endl;上述例子中的函式set中可以對型別為右值引用的形參k賦值，這是因為C++標準規定，具名的右值引用被當作左值。這一規定的意義在於，右值引用本來是用於實現移動語義，因而需要繫結一個物件的記憶體地址，然後具有修改這一物件內容的許可權，這些操作與左值繫結完全一樣。右值繫結與左值繫結的分別在於函式過載時的行為。對於移動構造成員函式與移動賦值運算子成員函式，其形、實引數結合時是按照右值引用處理；而在這兩個成語函式體內部，由於形參都是具名的，因而都被當作左值，這就可以用該形參來修改傳入物件的內部狀態。另外，右值引用作為xvalue本來是用於移動語義中一次性搬空其內容。具名使其具有更為持久的生存期，這是危險的，因而規定具名後為左值引用，除非程式顯式指定其型別強制轉換為右值引用。C++11標準給了使用者更大的決定權，例如把左值或臨終值，轉化為右值引用。這是透過定義在C++標準程式庫codice_2中的codice_3實現的。codice_3是個模板函式，把輸入的左值或右值轉換為右值引用型別的臨終值。其核心是強制型別轉換codice_5語句。除了左值引用、右值引用，對於函式引數型別是到模板引數型別的右值引用的情形，稱之為廣義引用。例如：template int foo(T&amp;&amp; param);詳見完美轉發一節的討論。完美轉發.完美轉發也是C++11標準引入右值引用這一概念所要實現的目標之一。背景.在C++程式設計中，一個常見的類工廠函式，如下例：templateshared_ptr factory(Arg arg)return shared_ptr( new T(arg) );引數物件arg在上例中是傳值方式傳遞，這帶來了生成額外的臨時物件的代價。對於類工廠函式，完美的引數傳遞應該是引用方式傳遞。因而，在codice_6中，引數是左值引用：templateshared_ptr factory(Arg&amp; arg)return shared_ptr( new T(arg) );這種實現的問題是形參不能繫結右值實參。如codice_7將編譯報錯。進一步解決辦法是按常量引用方式傳遞引數，如下例：templateshared_ptr factory(const Arg&amp; arg)return shared_ptr( new T(arg) );這種實現的問題是不能支援移動語義。形參使用右值引用可以解決完美轉發問題。引用摺疊規則.對於C++語言，不可以在源程式中直接對引用型別再施加引用。codice_8將編譯報錯。C++11標準中仍然禁止上述顯式對引用型別再施加引用，但如果在上下文環境中：模板引數型別推導.對函式模板codice_9，應用上述引用塌縮規則，可推匯出如下結論：這還適用於類模板的成員函式模板的型別推導：template  class vector {public:void push_back(T&amp;&amp; x); // T是類模板引數 ⇒ 該成員函式不需要型別推導;這裡的函式引數型別就是T的右值引用template  void emplace_back(Args&amp;&amp; args); // 該成員函式是個函式模板，有自己的模板引數，需要型別推導函式模板的形參必須是T&amp;&amp;形式，才需要模板引數型別推導。即使形參宣告為const T&amp;&amp;形式，就只能按字面意義使用，不需要模板引數型別推導。templatevoid f(const T&amp;&amp; param); // 這裡的“&amp;&amp;”不需要型別推導，意味著“常量型別T的右值引用”template的右值引用完美轉發的解決方案.templateshared_ptr factory(Arg&amp;&amp; arg)return shared_ptr(arg) ) );其中codice_10是定義在C++標準程式庫codice_2中的模板函式：template::type&amp; t )return static_cast(t);template::type&amp;&amp; t )return static_cast(t);codice_10在呼叫時，應當顯示給出該函式模板引數型別。其用途是：如果函式forward的實參的資料型別是左值引用，則返回型別為左值引用；如果函式forward的實參的資料型別是右值引用，則返回型別為右值引用，返回值的分類屬於臨終值，從而把引數的資訊完整地傳遞給下一級被呼叫的函式。從上述codice_10的定義實現來看，實參必須是個為左值的引用物件，但是實參的資料型別有兩種可能：類似的，codice_3也是利用上述模板函式型別推導規則，定義在C++標準程式庫codice_2中，無論輸入的實參是左值還是右值，均返回右值引用：templatetypename std::remove_reference::type&amp;&amp; move(T&amp;&amp; a) noexceptreturn static_cast(a);定義在C++標準程式庫codice_18中codice_19是個類模板，其中定義的型別codice_20是引用的基型別:template struct remove_reference {typedef T type;};template {typedef T type;};template {typedef T type;};移動語義與異常.移動語義只是把資源從一個右值物件搬移到被構造或者被賦值物件內部，因此保證不丟擲異常是容易實現的。在codice_21這樣的容器內實現移動語義，必須顯式宣告容器元素類的移動構造成員函式、移動賦值運算子成員函式不丟擲異常。右值引用的型別推導.C++中涉及到的右值引用的型別推導，除了上述模板引數型別推導，還有：auto關鍵字的型別完美轉發.C++11使用auto宣告變數時，如：codice_22“auto&amp;&amp;”並不意味著這一定是右值引用型別的變數，而是類似於模板函式引數的型別推導，既可能是左值引用，也可能是右值引用。其目的是把初始化表示式的值分類情況，完美轉發給由auto宣告的變數。也即：Type1&amp;&amp; var1=anotherType1Instance; // var1的型別是右值引用，但是作為左值auto&amp;&amp; var2=var1; //var2的型別是左值引用std::vector v;auto&amp;&amp; val = v[0]; // std::vector::operator[]的返回值是元素左值，所以val的型別是左值引用Widget makeWidget(); // 類工廠函式Widget&amp;&amp; var1 = makeWidget() // var1的型別是右值引用，具有左值。Widget var2 = static_cast(var1); // var2在初始化時可以使用移動建構函式。typedef的型別推導.typedef也可能會用到引用塌縮規則。例如：template class Widget {typedef T&amp; LvalueRefType;Widget w; // LvalueRefType的型別為int&amp;void f(Widget::LvalueRefType&amp;&amp; param); //param的型別為int&amp;decltype型別推導.decltype也可能會用到引用塌縮規則。例如：int var;decltype(var)&amp;&amp; v1=std::move(var); //型別是int&amp;&amp;