共用方式為


右值參考宣告子:&&

保留右值運算式的參考。

語法

rvalue-reference-type-id
type-specifier-seq && attribute-specifier-seqopt ptr-abstract-declaratoropt

備註

右值參考可讓您區分左值與右值。 左值參考和右值參考的語法和語意很相似,但是兩者遵循的規則稍有不同。 如需左值和右值的詳細資訊,請參閱左值和右值。 如需左值參考的詳細資訊,請參閱左值參考宣告子:&

下列章節說明右值參考如何支援「移動語意」(Move Semantics) 和「完美轉送」(Perfect Forwarding) 的實作。

移動語意

右值參考支援「移動語意」(Move Semantics) 實作,這可大幅提升應用程式的效能。 移動語意可讓您撰寫將資源從一個物件轉移到另一個物件 (例如動態配置記憶體) 的程式碼。 移動語意可用於轉移暫存物件中無法供程式其他位置參考的資源,因此可以發揮功效。

若要實作移動語意,通常需提供「移動建構函式」(move constructor) 和選擇性的移動指派運算子 (operator=) 給類別。 然後,來源為右值的複製和指派作業會自動利用移動語意。 不同於預設複製建構函式,編譯器不提供預設移動建構函式。 如需如何撰寫和使用移動建構函式的詳細資訊,請參閱移動建構函式和移動指派運算子

您也可以多載一般函式和運算子,以使用移動語意。 Visual Studio 2010 將移動語意引進至 C++ Standard 程式庫。 例如,string 類別會實作使用移動語意的作業。 請考慮下列串連數個字串並列印結果的範例:

// string_concatenation.cpp
// compile with: /EHsc
#include <iostream>
#include <string>
using namespace std;

int main()
{
   string s = string("h") + "e" + "ll" + "o";
   cout << s << endl;
}

在 Visual Studio 2010 之前,對 operator+ 的每個呼叫會配置並傳回新的暫存 string 物件 (右值)。 operator+ 無法將字串附加至其他字串,因為它不知道原始字串是左值還是右值。 如果原始字串都是左值,程式中其他位置可能會參考它們,因此不得加以修改。 您可以使用右值參考修改 operator+ 以採用右值,程式中的其他位置不能參考右值。 透過這項變更,operator+ 現在可以將一個字串附加至另一個字串。 這可以大幅減少 string 類別必須執行的動態記憶體配置數目。 如需 string 類別的詳細資訊,請參閱 basic_string 類別

當編譯器無法使用傳回值最佳化 (RVO) 或具名傳回值最佳化 (NRVO) 時,移動語意也能有所幫助。 在這些情況下,如果類型定義了移動建構函式,則編譯器會加以呼叫。

若要進一步了解移動語意,請參考將項目插入至 vector 物件的範例。 如果超出 vector 物件的容量,vector 物件必須為其元素重新配置足夠的記憶體,然後將每個元素複製到另一個記憶體位置,以便為插入的元素騰出空間。 當插入作業複製元素時,它會先建立新的元素。 然後,它會呼叫複製建構函式,將資料從上一個元素複製到新元素。 最後,它會終結前一個元素。 移動語意可讓您直接移動物件,而不必執行耗費大量資源的記憶體配置和複製作業。

若要利用 vector 範例中的移動語意,您可以寫入移動建構函式,將資料從某個物件移動至另一個。

如需在 Visual Studio 2010 中將語意移至 C++ Standard 程式庫的詳細資訊,請參閱 C++ Standard 程式庫

完美轉送

完美轉送可減少對多載函式的需求,有助於避免轉送問題。 當您撰寫採用參考作為其參數的泛型函式時,可能會發生轉送問題。 如果它會將這些參數傳遞 (或轉送) 至另一個函式,例如,如果它接受類型為 const T& 的參數,則呼叫的函式無法修改該參數的值。 如果泛型函式接受 T& 類型的參數,則無法使用右值 (例如暫存物件或整數常值) 呼叫函式。

若要解決這個問題,您通常必須提供每個參數採用 T&const T& 的泛型函式多載版本。 因此,多載函式數目會隨著參數數目以指數方式增加。 右值參考可讓您撰寫接受任意引數的函式版本。 然後,該函式可以將其轉送至另一個函式,如同直接呼叫另一個函式。

請考慮宣告 WXYZ 這四種類型的下列範例。 每個類型的建構函式接受 const 與非 const 左值參考的不同組合作為其參數。

struct W
{
   W(int&, int&) {}
};

struct X
{
   X(const int&, int&) {}
};

struct Y
{
   Y(int&, const int&) {}
};

struct Z
{
   Z(const int&, const int&) {}
};

假設您要撰寫產生物件的泛型函式。 下列範例示範撰寫此函式的其中一種方法:

template <typename T, typename A1, typename A2>
T* factory(A1& a1, A2& a2)
{
   return new T(a1, a2);
}

下列範例示範對 factory 函式的有效呼叫:

int a = 4, b = 5;
W* pw = factory<W>(a, b);

不過,下列範例不包含對 factory 函式的有效呼叫。 這是因為 factory 採用可修改為其參數的左值參考,但會使用右值來呼叫:

Z* pz = factory<Z>(2, 2);

若要解決這個問題,您通常必須為每個 factoryA& 參數的組合建立 const A& 函式的多載版本。 右值參考可讓您撰寫 factory 函式的一個版本,如下列範例所示:

template <typename T, typename A1, typename A2>
T* factory(A1&& a1, A2&& a2)
{
   return new T(std::forward<A1>(a1), std::forward<A2>(a2));
}

這個範例使用右值參考做為 factory 函式的參數。 std::forward 函式的用途是將 Factory 函式的參數轉送至範本類別的建構函式。

下列範例示範 main 函式,這個函式會使用修改的 factory 函式來建立 WXYZ 類別的執行個體。 修改的 factory 函式會將其參數 (左值或右值) 轉送至適當的類別建構函式。

int main()
{
   int a = 4, b = 5;
   W* pw = factory<W>(a, b);
   X* px = factory<X>(2, b);
   Y* py = factory<Y>(a, 2);
   Z* pz = factory<Z>(2, 2);

   delete pw;
   delete px;
   delete py;
   delete pz;
}

右值參考的屬性

您可以多載函式以接受左值參考和右值參考。

透過多載函式來接受 const 左值參考或右值參考時,您撰寫的程式碼可以區別不可修改的物件 (左值) 和可修改的暫存值 (右值)。 您可以將物件傳遞至接受右值參考的函式,除非該物件標記為 const。 下列範例說明多載之後可接受左值參考和右值參考的 f 函式。 main 函式會使用左值和右值呼叫 f

// reference-overload.cpp
// Compile with: /EHsc
#include <iostream>
using namespace std;

// A class that contains a memory resource.
class MemoryBlock
{
   // TODO: Add resources for the class here.
};

void f(const MemoryBlock&)
{
   cout << "In f(const MemoryBlock&). This version can't modify the parameter." << endl;
}

void f(MemoryBlock&&)
{
   cout << "In f(MemoryBlock&&). This version can modify the parameter." << endl;
}

int main()
{
   MemoryBlock block;
   f(block);
   f(MemoryBlock());
}

這個範例會產生下列輸出:

In f(const MemoryBlock&). This version can't modify the parameter.
In f(MemoryBlock&&). This version can modify the parameter.

在此範例中,第一個對 f 的呼叫會傳遞區域變數 (左值) 做為其引數。 對 f 的第二個呼叫會傳遞暫存物件做為其引數。 由於程式中的其他位置無法參考暫存物件,呼叫會繫結至接受右值參考的 f 多載版本,此版本可以自由修改物件。

編譯器會將具名右值參考視為左值,而將未具名右值參考當做右值。

採用右值參考作為參數的函式會將參數視為函式本文中的左值。 編譯器會將具名右值參考視為左值。 這是因為程式有數個部分可以參考具名物件。 允許程式的多個部分修改或移除該物件的資源會很危險。 例如,如果程式中有多個部分嘗試從相同物件傳輸資源,則只有第一個會成功傳輸。

下列範例說明多載之後可接受左值參考和右值參考的 g 函式。 f 函式接受右值參考做為其參數 (具名右值參考),並傳回右值參考 (未具名右值參考)。 從 gf 的呼叫中,多載解析會選取採用左值參考的 g 版本,因為 f 主體會將其參數視為左值。 從 gmain 的呼叫中,多載解析會選取採用右值參考的 g 版本,因為 f 會傳回右值參考。

// named-reference.cpp
// Compile with: /EHsc
#include <iostream>
using namespace std;

// A class that contains a memory resource.
class MemoryBlock
{
   // TODO: Add resources for the class here.
};

void g(const MemoryBlock&)
{
   cout << "In g(const MemoryBlock&)." << endl;
}

void g(MemoryBlock&&)
{
   cout << "In g(MemoryBlock&&)." << endl;
}

MemoryBlock&& f(MemoryBlock&& block)
{
   g(block);
   return move(block);
}

int main()
{
   g(f(MemoryBlock()));
}

這個範例會產生下列輸出:

In g(const MemoryBlock&).
In g(MemoryBlock&&).

在範例中,main 函式將右值傳遞給 ff 主體會將其具名參數視為左值。 從 fg 的呼叫會將參數繫結至左值參考 (g 的第一個多載版本)。

  • 您無法將左值轉換為右值參考。

C++ Standard 程式庫 std::move 函式可用於將物件轉換為該物件的右值參考。 如下列範例所示,您也可以使用 static_cast 關鍵字將左值轉型為右值參考:

// cast-reference.cpp
// Compile with: /EHsc
#include <iostream>
using namespace std;

// A class that contains a memory resource.
class MemoryBlock
{
   // TODO: Add resources for the class here.
};

void g(const MemoryBlock&)
{
   cout << "In g(const MemoryBlock&)." << endl;
}

void g(MemoryBlock&&)
{
   cout << "In g(MemoryBlock&&)." << endl;
}

int main()
{
   MemoryBlock block;
   g(block);
   g(static_cast<MemoryBlock&&>(block));
}

這個範例會產生下列輸出:

In g(const MemoryBlock&).
In g(MemoryBlock&&).

函式範本會推算其範本引數類型,然後使用參考摺疊規則。

可以將其參數傳遞 (或轉送) 至另一個函式的函式範本是常見模式。 請務必了解範本類型推算對於接受右值參考之函式範本的作用。

如果函式引數為右值,編譯器會推算引數為右值參考。 例如,假設您將類型 X 物件的右值參考傳遞至採用類型 T&& 作為其參數的函式範本。 範本引數推算會將 T 推算為 X,因此參數的類型為 X&&。 如果函式引數為左值或 const 左值,編譯器會推算其類型為該類型的左值參考或 const 左值參考。

下列範例宣告某個結構樣板,然後針對各種參考類型特製化此樣板。 print_type_and_value 函式接受右值參考做為其參數,並將其轉送給 S::print 方法的適當特製化版本。 main 函式示範各種呼叫 S::print 方法的方式。

// template-type-deduction.cpp
// Compile with: /EHsc
#include <iostream>
#include <string>
using namespace std;

template<typename T> struct S;

// The following structures specialize S by
// lvalue reference (T&), const lvalue reference (const T&),
// rvalue reference (T&&), and const rvalue reference (const T&&).
// Each structure provides a print method that prints the type of
// the structure and its parameter.

template<typename T> struct S<T&> {
   static void print(T& t)
   {
      cout << "print<T&>: " << t << endl;
   }
};

template<typename T> struct S<const T&> {
   static void print(const T& t)
   {
      cout << "print<const T&>: " << t << endl;
   }
};

template<typename T> struct S<T&&> {
   static void print(T&& t)
   {
      cout << "print<T&&>: " << t << endl;
   }
};

template<typename T> struct S<const T&&> {
   static void print(const T&& t)
   {
      cout << "print<const T&&>: " << t << endl;
   }
};

// This function forwards its parameter to a specialized
// version of the S type.
template <typename T> void print_type_and_value(T&& t)
{
   S<T&&>::print(std::forward<T>(t));
}

// This function returns the constant string "fourth".
const string fourth() { return string("fourth"); }

int main()
{
   // The following call resolves to:
   // print_type_and_value<string&>(string& && t)
   // Which collapses to:
   // print_type_and_value<string&>(string& t)
   string s1("first");
   print_type_and_value(s1);

   // The following call resolves to:
   // print_type_and_value<const string&>(const string& && t)
   // Which collapses to:
   // print_type_and_value<const string&>(const string& t)
   const string s2("second");
   print_type_and_value(s2);

   // The following call resolves to:
   // print_type_and_value<string&&>(string&& t)
   print_type_and_value(string("third"));

   // The following call resolves to:
   // print_type_and_value<const string&&>(const string&& t)
   print_type_and_value(fourth());
}

這個範例會產生下列輸出:

print<T&>: first
print<const T&>: second
print<T&&>: third
print<const T&&>: fourth

為了解析對 print_type_and_value 函式的每個呼叫,編譯器會先執行範本引數推算。 編譯器以推算出的範本引數取代參數類型時,會接著套用參考摺疊規則。 例如,將區域變數 s1 傳遞至 print_type_and_value 函式會造成編譯器產生下列函式簽章:

print_type_and_value<string&>(string& && t)

編譯器會使用參考摺疊規則以縮減簽章:

print_type_and_value<string&>(string& t)

print_type_and_value 函式的這個版本接著會將其參數轉送給 S::print 方法的正確特製化版本。

下表摘要說明樣板引數類型推算的參考摺疊規則:

展開的類型 摺疊的類型
T& & T&
T& && T&
T&& & T&
T&& && T&&

樣板引數推算是實作完美轉送的要素。 [完美轉送] 區段會更詳細地描述完美的轉送。

摘要

右值參考會區分左值與右值。 若要改善應用程式效能,它們可以消除不必要的記憶體配置和複製作業。 它們也可讓您撰寫接受任意引數的函式。 該函式可以將其轉送至另一個函式,如同直接呼叫另一個函式。

另請參閱

具有一元運算子的運算式
左值參考宣告子:&
左值和右值
移動建構函式和移動指派運算子 (C++)
C++ 標準程式庫