完美转发

**右值參照**（rvalue reference），是 C++ 程式設計語言自 C++11 標準提出的一類資料類型。用於實現移動語意（move semantic）與完美轉發（perfect forwarding）。

## 背景

在 C++11 提出右值參照之前，C++03 及更早的 C++ 標準中，表達式的「值分類」（value categories）屬性為左值或右值。左值是對應（refer to）記憶體中有確定儲存位址的物件之表達式的值，而右值是所有不是左值之表達式的值。因而，右值可以是字面量、臨時物件等表達式。能否被賦值不是區分 C++ 左值與右值的依據，C++ 的 const 左值是不可賦值的；而作為臨時物件的右值可能允許被賦值。左值與右值的根本區別在於是否允許取址運算子（&）獲得對應的記憶體位址。C++03 及以前的標準定義了在表達式中左值到右值的三類隱式自動轉換：

* 左值轉化為右值；如整數變數 i 在表達式 （i+3）
* 陣列名是常數左值，在表達式中轉化為陣列首元素的位址值
* 函式名是常數左值，在表達式中轉化為函式的位址值

作為一種追求執行效率的語言，C++ 在用臨時物件或函式返回值給左值物件賦值時的深度拷貝（deep copy）一直受到詬病。考慮到臨時物件的生命期僅在表達式中持續，如果把臨時物件的內容直接移動（move）給被賦值的左值物件，效率改善將是顯著的。這就是**移動語意**的來源。

與傳統的拷貝設定運算子（copy assignment）成員函式、拷貝構造（copy ctor）成員函式對應，移動語意需要有移動賦值（move assignment）成員函式、移動構造（move ctor）成員函式的實現機制。可以通過函式多載來確定是呼叫拷貝語意還是移動語意的實現。

右值參照就是為了實現移動語意與完美轉發所需要而設計出來的新的資料類型。右值參照的實例對應於臨時物件；右值參照並區別於左值參照，用作形參時能多載辨識（overload resolution）是呼叫拷貝語意還是移動語意的函式。

無論是傳統的左值參照還是 C++11 引進的右值參照，從編譯後的反組譯層面上，都是物件的儲存位址與自動解參照（dereference，取址的相反）。因此，右值參照與左值參照的變數都不能懸空 (dangling)，也即定義時必須初始化從而繫結到一個物件上。右值參照變數繫結的物件，是編程者認為可以通過移動語意移走其內容的物件，對這種物件就需要定義為一種獨特的值分類，即 C++11 標準稱之為「臨終值」（eXpire Value）。臨終值物件既有儲存位址因此可以繫結到右值參照變數上，而且它又是一個即將停止使用的物件可以被移走內容。所以臨終值既不同於左值，也不同於傳統的右值（C++11 稱之為純右值），不能取位址運算（&）。另一方面，臨終值兼有傳統的左值與右值的性質：既對應於一個（臨時）物件，稱之為有標識（identity）；同時其內容可以移走，稱之為抽取式性（movability）。C++11 標準把臨終值與左值合稱為廣義左值，即指向某個物理存在的物件；把臨終值與純右值（對應 C++03 時的右值概念）合稱為右值（C++11 重新定義的概念），其內容可以移走（該右值生命期到此為止，此後將不再使用）。之所以稱為右值而不叫做廣義右值，是因為右值參照即可以與臨終值物件繫結，也可以與純右值物件繫結（這時往往自動生成一個臨時物件）。

C++ 語言在引入了右值參照之後，面臨著一個問題：如何讓編程者指出哪個物件具有臨終值？這有兩種顯式指定方法：如果函式（或運算子）的返回類型為右值參照，或者通過類型轉換如 static\_cast<Type&&> 或者 std::move () 模板函式。

## 定義

設 X 為任何一種資料類型，則定義 X&& 是**到資料類型 X 的右值參照**（rvalue reference to X）。傳統的參照類型 X & 被稱為左值參照（lvalue reference to X）。例如：

```
int i;
int &j=i; //傳統的左值引用數據類型的變量並初始化
int &&k=std::move(i);  //定義一個右值引用數據類型的變量並初始化。std::move定義於<utility>
```

## 語意

右值參照是一種資料類型，既有右值性質，也有左值性質。在 C++11 中為右值參照專門定義了**臨終值**（eXpiring value）這一概念。右值參照很類似於傳統的左值參照。

例如：

C++ 中，參照（reference）是指繫結到記憶體中的相應物件上。左值參照是繫結到左值物件上；右值參照是繫結到臨時物件上。這裡的左值物件是指可以通過取位址 & 運算子得到該物件的記憶體位址；而臨時物件是不能用取位址 & 運算子取得到物件的記憶體位址。

C++11 有如下**參照繫結規則**：

* 非常數左值參照（X& ）：只能繫結到 X 類型的左值物件；
* 常數左值參照（const X&）：可以繫結到 X、const X 類型的左值物件，或 X、const X 類型的右值；
* 非常數右值參照（X&&）：只能繫結到 X 類型的右值；
* 常數右值參照（const X&&）：可以繫結規定到 X、const X 類型的右值。

對上述參照繫結規則，值得注意的是：

1. 常數左值參照、常數右值參照、非常數右值參照，在繫結到字面量（literal）時，實際上用字面量構造了一個物件，然後繫結到該物件上；
2. 上述參照繫結規定所提到的右值，包含了純右值與臨終值（xvalue）。即右值參照可以繫結到純右值（隱式自動構造的臨時物件），也可以繫結到臨終值物件。
3. 繫結規則在呼叫多載函式，虛、實參數結合時起到決定作用，可以確定哪個版本的多載函式被呼叫。一些函式的形參為右值參照類型，因此可接受右值實參的繫結，如移動建構函式、移動設定運算子、正常的成員函式如 `std::vector::push_back`；另一些函式的形參為左值參照類型，因此可接受左值實參的繫結。一些函式模板的形參是廣義參照（universal reference，詳見下文)，即可以接受左值物件繫結，也可以接受臨時物件繫結，需要在模板推導時來決定形參是左值參照還是右值參照，這給了一套函式模板以極大地靈活性，可以同時處理兩種參照類型，可以把函式參數的參照類型「完美轉發」（詳見下文）給被呼叫的實現函式。
4. C++0X 曾經規定右值參照可以繫結到左值物件上，但在 C++11 中取消了這一許可。

由於右值參照主要針對移動語意用來修改被參照的物件的內容，所以常數右值參照（const X&&）較少用到。

函式返回值是右值資料類型還是右值參照類型，區別在於前者是「傳值」，後者是「傳參照」可以修改被參照的物件。例如：

上述例子中的函式 set 中可以對類型為右值參照的形參 k 賦值，這是因為 C++ 標準規定，**具名的右值參照被當作左值**。這一規定的意義在於，右值參照本來是用於實現移動語意，因而需要繫結一個物件的記憶體位址，然後具有修改這一物件內容的權限，這些操作與左值繫結完全一樣。右值繫結與左值繫結的分別在於函式多載時的行為。對於移動構造成員函式與移動設定運算子成員函式，其形、實參數結合時是按照右值參照處理；而在這兩個成語函式體內部，由於形參都是具名的，因而都被當作左值，這就可以用該形參來修改傳入物件的內部狀態。另外，右值參照作為 xvalue（臨終值）本來是用於移動語意中一次性搬空其內容。具名使其具有更為持久的生存期，這是危險的，因而規定具名後為左值參照，除非程式顯式指定其類型強制轉換為右值參照。

C++11 標準給了使用者更大的決定權，例如把左值或臨終值，轉化為右值參照。這是通過定義在 C++ 標準程式庫 `<utility>` 中的 `**std::move**` 實現的。`std::move` 是個模板函式，把輸入的左值或右值轉換為右值參照類型的臨終值。其核心是強制類型轉換 `static_cast<Type&&>()` 語句。

除了左值參照、右值參照，對於函式參數類型是到模板參數類型的右值參照的情形，稱之為廣義參照（universal reference）或轉發參照（forward reference）。例如：

```
 template<typename T> int foo(T&& param);
```

詳見完美轉發一節的討論。

## 完美轉發

完美轉發也是 C++11 標準引入右值參照這一概念所要實現的目標之一。

### 背景

在 C++ 程式設計中，一個常見的類工廠函式，如下例：

參數物件 arg 在上例中是傳值方式傳遞，這帶來了生成額外的臨時物件的代價。對於類工廠函式，完美的參數傳遞應該是參照方式傳遞。因而，在 `boost:bind` 中，參數是左值參照：

這種實現的問題是形參不能繫結右值實參。如 `factory<X>(102)` 將編譯報錯。進一步解決辦法是按常數參照方式傳遞參數，如下例：

這種實現的問題是不能支援移動語意。

形參使用右值參照可以解決完美轉發問題。

### 參照摺疊規則

對於 C++ 語言，不可以在源程式中直接對參照類型再施加參照。`T& &` 將編譯報錯。C++11 標準中仍然禁止上述顯式對參照類型再施加參照，但如果在上下文環境中（包括模板實例化、typedef、auto 類型推斷等）如出現了對參照類型再施加參照，則施行**參照塌縮規則**（reference collapsing rule）：

* T& & 變為 T&
* T& && 變為 T&
* T&& & 變為 T&
* T&& && 變為 T&&

### 模板參數類型推導

對函式模板 `template<typename T>void foo(T&&);`，應用上述參照塌縮規則，可推導出如下結論：

* 如果實參是類型 A 的左值，則模板參數 T 的類型為 A&，形參類型為 A&；
* 如果實參是類型 A 的右值，則模板參數 T 的類型為 A&&，形參類型為 A&&。

這還適用於類別模板的成員函式模板的類型推導：

函式模板的形參必須是 T&& 形式，才需要模板參數類型推導。即使形參聲明為 const T&& 形式，就只能按字面意義使用，不需要模板參數類型推導。

### 完美轉發的解決方案

其中 `std::forward` 是定義在 C++ 標準程式庫 `<utility>` 中的模板函式：

`std::forward` 在呼叫時，應當顯示給出該函式模板參數類型。其用途是：如果函式 forward 的實參的資料類型是左值參照，則返回類型為左值參照；如果函式 forward 的實參的資料類型是右值參照，則返回類型為右值參照，返回值的分類屬於臨終值，從而把參數的資訊完整地傳遞給下一級被呼叫的函式。從上述 `std::forward` 的定義實現來看，實參必須是個為左值的參照物件，但是實參的資料類型有兩種可能：

* 實參的資料類型 S 是左值參照類型，`std::forward` 的返回類型 S&& 根據參照塌縮規則變為 S&，即返回值仍為左值參照類型；
* 實參的資料類型 S 是右值參照類型（這是因為右值參照類型的具名變數實際上表現為左值），`std::forward` 的返回類型 S&& 根據參照塌縮規則變為 S&&，即返回值為右值參照類型。

類似的，`std::move` 也是利用上述模板函式類型推導規則，定義在 C++ 標準程式庫 `<utility>` 中，無論輸入的實參是左值還是右值，均返回右值參照：

定義在 C++ 標準程式庫 `<type_traits>` 中 **`std::remove_reference`** 是個類別模板，其中定義的類型 `type` 是參照的基本類型:

## 移動語意與異常

移動語意只是把資源從一個右值物件搬移到被構造或者被賦值物件內部，因此保證不丟擲異常是容易實現的。在 `std::vector` 這樣的容器內實現移動語意，必須顯式聲明容器元素類的移動構造成員函式、移動設定運算子成員函式不丟擲異常。

## 右值參照的類型推導

C++ 中涉及到的右值參照的類型推導，除了上述模板參數類型推導，還有：

### auto 關鍵字的類型完美轉發

C++11 使用 auto 聲明變數時，如：`auto&& var=initValue;`「auto&&」並不意味著這一定是**右值參照**類型的變數，而是類似於模板函式參數的類型推導，既可能是左值參照，也可能是右值參照。其目的是把初始化表達式的值分類情況，完美轉發給由 auto 聲明的變數。也即：

* 如果初始化值（initializer）是類型 A 的左值，則聲明的變數類型為左值參照 A&；
* 如果初始化值是類型 A 的右值，則聲明的變數類型為右值參照 A&&。

### typedef 的類型推導

typedef 也可能會用到參照塌縮規則。例如：

### decltype 類型推導

decltype 也可能會用到參照塌縮規則。例如：

## 備註

## 參考文獻