Rvalue 參考宣告子: &&
保留右值運算式的參考。
語法
rvalue-reference-type-id
:
type-specifier-seq
&&
attribute-specifier-seq
opt optptr-abstract-declarator
備註
右值參考可讓您區分左值與右值。 Lvalue 參考和右值參考在語法上和語意上相似,但會遵循稍微不同的規則。 如需左值和右值的詳細資訊,請參閱 左值和右值。 如需左值參考的詳細資訊,請參閱 Lvalue 參考宣告子: 和 。
下列各節說明右值參考如何支持移動語意的實作和完美的轉送。
移動語意
右值參考支持行動語意的實作,這可大幅提升應用程式的效能。 移動語意可讓您撰寫將資源從一個物件轉移到另一個物件 (例如動態配置記憶體) 的程式碼。 移動語意的運作方式是因為它能夠從暫存物件傳輸資源:無法在程式中其他地方參考的資源。
若要實作行動語意,您通常會提供 行動建構函式, 以及選擇性地將移動指派運算子 (operator=
) 提供給類別。 然後,來源為右值的複製和指派作業會自動利用移動語意。 不同於預設複製建構函式,編譯程式不會提供預設移動建構函式。 如需如何撰寫和使用移動建構函式的詳細資訊,請參閱 移動建構函式和移動指派運算符。
您也可以多載一般函式和運算子,以使用移動語意。 Visual Studio 2010 引進將語意移至 C++ 標準連結庫。 例如,類別 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+
無法將一個字串附加至另一個字串,因為它不知道來源字串是否為左值或右值。 如果來源字串都是左值,則程式中的其他位置可能會參考它們,因此不得修改。 您可以使用 rvalue 參考來修改 operator+
以取得右值,而此參考無法在程式中其他地方參考。 透過這項變更, operator+
現在可以將一個字串附加至另一個字串。 此變更可大幅減少類別必須執行的易失記憶體配置 string
數目。 如需 類別 string
的詳細資訊,請參閱 basic_string
類別。
當編譯程式無法使用傳回值優化 (RVO) 或具名傳回值優化 (NRVO) 時,移動語意也會有所説明。 在這些情況下,如果類型定義了移動建構函式,則編譯器會加以呼叫。
若要進一步了解移動語意,請參考將項目插入至 vector
物件的範例。 如果超過物件的容量 vector
, vector
對象必須為其元素重新配置足夠的記憶體,然後將每個元素複製到另一個記憶體位置,以騰出空間供插入的專案使用。 當插入作業複製專案時,它會先建立新的專案。 然後,它會呼叫複製建構函式,將數據從上一個專案複製到新元素。 最後,它會終結前一個專案。 移動語意可讓您直接移動物件,而不需要進行昂貴的記憶體配置和複製作業。
若要利用 vector
範例中的移動語意,您可以寫入移動建構函式,將資料從某個物件移動至另一個。
如需在 Visual Studio 2010 中將語意移至 C++ 標準連結庫的詳細資訊,請參閱 C++ 標準連結庫。
完美轉寄
完美轉送可減少對多載函式的需求,有助於避免轉送問題。 當您 撰寫採用參考做為其參數的泛型函式時,可能會發生轉送問題 。 如果它會將這些參數傳遞至另一個函式,例如,如果它接受 類型的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
左值參考或右值參考,您可以撰寫程式代碼來區分不可修改的物件 (lvalues) 和可修改的暫存值 (rvalues)。 除非物件標示為 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++ 標準連結庫 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&& |
樣板引數推算是實作完美轉送的要素。 [完美轉寄] 區段會更詳細地描述完美的轉送。
摘要
右值參考會區分左值與右值。 為了改善應用程式的效能,它們可以消除不必要的記憶體配置和複製作業的需求。 它們也可讓您撰寫可接受任意自變數的函式。 該函式可以將它們轉送至另一個函式,就像直接呼叫另一個函式一樣。
另請參閱
具有一元運算子的表達式
Lvalue 參考宣告子: &
左值和右值
移動建構函式和移動指派運算子 (C++)
C++ 標準程式庫
意見反映
https://aka.ms/ContentUserFeedback。
即將推出:我們會在 2024 年淘汰 GitHub 問題,並以全新的意見反應系統取代並作為內容意見反應的渠道。 如需更多資訊,請參閱:提交及檢視以下的意見反映: