右值參考宣告子:
保留右值運算式的參考。
語法
rvalue-reference-type-id
:
type-specifier-seq
&&
attribute-specifier-seq
opt ptr-abstract-declarator
opt
備註
右值參考可讓您區分左值與右值。 左值參考和右值參考的語法和語意很相似,但是兩者遵循的規則稍有不同。 如需左值和右值的詳細資訊,請參閱左值和右值。 如需左值參考的詳細資訊,請參閱左值參考宣告子:&。
下列章節說明右值參考如何支援「移動語意」(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&
的泛型函式多載版本。 因此,多載函式數目會隨著參數數目以指數方式增加。 右值參考可讓您撰寫接受任意引數的函式版本。 然後,該函式可以將其轉送至另一個函式,如同直接呼叫另一個函式。
請考慮宣告 W
、X
、Y
和 Z
這四種類型的下列範例。 每個類型的建構函式接受 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);
若要解決這個問題,您通常必須為每個 factory
與 A&
參數的組合建立 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
函式來建立 W
、X
、Y
和 Z
類別的執行個體。 修改的 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
函式接受右值參考做為其參數 (具名右值參考),並傳回右值參考 (未具名右值參考)。 從 g
對 f
的呼叫中,多載解析會選取採用左值參考的 g
版本,因為 f
主體會將其參數視為左值。 從 g
對 main
的呼叫中,多載解析會選取採用右值參考的 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
函式將右值傳遞給 f
。 f
主體會將其具名參數視為左值。 從 f
對 g
的呼叫會將參數繫結至左值參考 (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&& |
樣板引數推算是實作完美轉送的要素。 [完美轉送] 區段會更詳細地描述完美的轉送。
摘要
右值參考會區分左值與右值。 若要改善應用程式效能,它們可以消除不必要的記憶體配置和複製作業。 它們也可讓您撰寫接受任意引數的函式。 該函式可以將其轉送至另一個函式,如同直接呼叫另一個函式。