引用宣告
宣告一個命名變數為引用,即一個已存在的物件或函式的別名。
目錄 |
[編輯] 語法
引用變數宣告是任何簡單宣告,其宣告符具有以下形式
& attr (可選) declarator |
(1) | ||||||||
&& attr (可選) declarator |
(2) | (C++11 起) | |||||||
D
宣告為 decl-specifier-seq S
確定的型別的*左值引用*。D
宣告為 decl-specifier-seq S
確定的型別的*右值引用*。宣告符 | - | 任何宣告符,除了另一個引用宣告符(不存在對引用的引用) |
屬性 | - | (自 C++11 起) 屬性列表 |
引用要求被初始化以引用有效的物件或函式:參見引用初始化。
不能形成型別“對(可能帶cv限定的)void 的引用”。
引用型別不能在頂層進行cv限定;宣告中沒有這種語法,如果將限定符新增到 typedef-name 或 decltype
說明符,(自 C++11 起) 或 型別模板引數,則會被忽略。
引用不是物件;它們不一定佔用儲存空間,儘管編譯器可能會在必要時分配儲存空間以實現所需的語義(例如,引用型別的非靜態資料成員通常會使類的大小增加儲存記憶體地址所需的量)。
因為引用不是物件,所以沒有引用陣列,沒有指向引用的指標,也沒有對引用的引用。
int& a[3]; // error int&* p; // error int& &r; // error
引用摺疊允許透過模板或 typedef 中的型別操作形成對引用的引用,在這種情況下,應用*引用摺疊*規則:右值引用到右值引用摺疊為右值引用,所有其他組合形成左值引用 typedef int& lref; typedef int&& rref; int n; lref& r1 = n; // type of r1 is int& lref&& r2 = n; // type of r2 is int& rref& r3 = n; // type of r3 is int& rref&& r4 = 1; // type of r4 is int&& (這與當在函式模板中使用 |
(C++11 起) |
[編輯] 左值引用
左值引用可用於別名現有物件(可選具有不同的cv限定)
#include <iostream> #include <string> int main() { std::string s = "Ex"; std::string& r1 = s; const std::string& r2 = s; r1 += "ample"; // modifies s // r2 += "!"; // error: cannot modify through reference to const std::cout << r2 << '\n'; // prints s, which now holds "Example" }
它們也可以用於在函式呼叫中實現按引用傳遞語義
#include <iostream> #include <string> void double_string(std::string& s) { s += s; // 's' is the same object as main()'s 'str' } int main() { std::string str = "Test"; double_string(str); std::cout << str << '\n'; }
當函式的返回型別是左值引用時,函式呼叫表示式成為一個左值表示式
#include <iostream> #include <string> char& char_number(std::string& s, std::size_t n) { return s.at(n); // string::at() returns a reference to char } int main() { std::string str = "Test"; char_number(str, 1) = 'a'; // the function call is lvalue, can be assigned to std::cout << str << '\n'; }
右值引用右值引用可用於延長臨時物件的生命週期(注意,指向 const 的左值引用也可以延長臨時物件的生命週期,但不能透過它們進行修改) 執行此程式碼 #include <iostream> #include <string> int main() { std::string s1 = "Test"; // std::string&& r1 = s1; // error: can't bind to lvalue const std::string& r2 = s1 + s1; // okay: lvalue reference to const extends lifetime // r2 += "Test"; // error: can't modify through reference to const std::string&& r3 = s1 + s1; // okay: rvalue reference extends lifetime r3 += "Test"; // okay: can modify through reference to non-const std::cout << r3 << '\n'; } 更重要的是,當一個函式同時具有右值引用和左值引用過載時,右值引用過載繫結到右值(包括prvalue和xvalue),而左值引用過載繫結到左值。 執行此程式碼 #include <iostream> #include <utility> void f(int& x) { std::cout << "lvalue reference overload f(" << x << ")\n"; } void f(const int& x) { std::cout << "lvalue reference to const overload f(" << x << ")\n"; } void f(int&& x) { std::cout << "rvalue reference overload f(" << x << ")\n"; } int main() { int i = 1; const int ci = 2; f(i); // calls f(int&) f(ci); // calls f(const int&) f(3); // calls f(int&&) // would call f(const int&) if f(int&&) overload wasn't provided f(std::move(i)); // calls f(int&&) // rvalue reference variables are lvalues when used in expressions int&& x = 1; f(x); // calls f(int& x) f(std::move(x)); // calls f(int&& x) } 這使得移動建構函式、移動賦值運算子以及其他支援移動的函式(例如std::vector::push_back())在合適時能夠自動選擇。 因為右值引用可以繫結到 xvalues,所以它們可以引用非臨時物件 int i2 = 42; int&& rri = std::move(i2); // binds directly to i2 這使得可以從不再需要的範圍內物件中移動 std::vector<int> v{1, 2, 3, 4, 5}; std::vector<int> v2(std::move(v)); // binds an rvalue reference to v assert(v.empty()); 轉發引用轉發引用是一種特殊的引用,它保留函式引數的值類別,使其能夠透過std::forward進行*轉發*。轉發引用可以是 1) 宣告為該函式模板的cv非限定型別模板引數的右值引用的函式模板的函式引數
template<class T> int f(T&& x) // x is a forwarding reference { return g(std::forward<T>(x)); // and so can be forwarded } int main() { int i; f(i); // argument is lvalue, calls f<int&>(int&), std::forward<int&>(x) is lvalue f(0); // argument is rvalue, calls f<int>(int&&), std::forward<int>(x) is rvalue } template<class T> int g(const T&& x); // x is not a forwarding reference: const T is not cv-unqualified template<class T> struct A { template<class U> A(T&& x, U&& y, int* p); // x is not a forwarding reference: T is not a // type template parameter of the constructor, // but y is a forwarding reference }; auto&& vec = foo(); // foo() may be lvalue or rvalue, vec is a forwarding reference auto i = std::begin(vec); // works either way (*i)++; // works either way g(std::forward<decltype(vec)>(vec)); // forwards, preserving value category for (auto&& x: f()) { // x is a forwarding reference; this is a common way to use range for in generic code } auto&& z = {1, 2, 3}; // *not* a forwarding reference (special case for initializer lists) 另請參閱模板引數推導和std::forward。 |
(C++11 起) |
[編輯] 懸空引用
儘管引用在初始化時總是引用有效的物件或函式,但可能會建立程式,其中被引用物件的生命週期結束,但引用仍然可訪問(*懸空*)。
給定一個引用型別的表示式 expr,並令 target 為該引用所指的物件或函式
- 如果在 expr 求值上下文中指向 target 的指標是有效的,則結果指代 target。
- 否則,行為未定義。
std::string& f() { std::string s = "Example"; return s; // exits the scope of s: // its destructor is called and its storage deallocated } std::string& r = f(); // dangling reference std::cout << r; // undefined behavior: reads from a dangling reference std::string s = f(); // undefined behavior: copy-initializes from a dangling reference
請注意,右值引用和指向const的左值引用會延長臨時物件的生命週期(有關規則和例外,請參閱引用初始化)。
如果被引用物件已被銷燬(例如透過顯式解構函式呼叫),但儲存未被釋放,則對已超出生命週期物件的引用可以以有限的方式使用,並且如果物件在相同的儲存中重新建立,則可能變得有效(有關詳細資訊,請參閱超出生命週期的訪問)。
[編輯] 型別不可訪問引用
嘗試將引用繫結到物件,其中轉換後的初始化器是左值(直到 C++11)glvalue(自 C++11 起),透過該glvalue物件不是型別可訪問的,會導致未定義行為
char x alignas(int); int& ir = *reinterpret_cast<int*>(&x); // undefined behavior: // initializer refers to char object
[編輯] 呼叫不相容引用
嘗試將引用繫結到一個函式,其中轉換後的初始化器是一個左值(直到 C++11)一個glvalue(自 C++11 起),其型別與函式定義的型別不相容,會導致未定義行為
void f(int); using F = void(float); F& ir = *reinterpret_cast<F*>(&f); // undefined behavior: // initializer refers to void(int) function
[編輯] 注意
功能測試宏 | 值 | 標準 | 特性 |
---|---|---|---|
__cpp_rvalue_references |
200610L |
(C++11) | 右值引用 |
[編輯] 缺陷報告
下列更改行為的缺陷報告追溯地應用於以前出版的 C++ 標準。
缺陷報告 | 應用於 | 釋出時的行為 | 正確的行為 |
---|---|---|---|
CWG 453 | C++98 | 不清楚引用不能繫結到哪個物件或函式 | 已明確 |
CWG 1510 | C++11 | 在 decltype 的運算元中無法形成 cv-qualified 引用 | 允許 |
CWG 2550 | C++98 | 引數可以有型別“對 void 的引用” | 已停用 |
CWG 2933 | C++98 | 訪問懸空引用的行為不明確 | 已明確 |
[編輯] 外部連結
Thomas Becker, 2013 - C++ 右值引用解釋 |