引用声明
将具名变量声明为引用,即已存在对象或函数的别名。
内容 |
[编辑] 语法
引用变量声明是任何简单声明,其声明符的形式为
& attr (可选) 声明符 |
(1) | ||||||||
&& attr (可选) 声明符 |
(2) | (自 C++11 起) | |||||||
D
声明为由decl-specifier-seq S
确定的类型的左值引用。D
声明为由decl-specifier-seq S
确定的类型的右值引用。声明符 | - | 任何声明符,但另一个引用声明符除外(没有引用的引用) |
attr | - | (自 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'; }
右值引用右值引用可以用于延长临时对象的生命周期(请注意,常量左值引用也可以延长临时对象的生命周期,但不能通过它们进行修改) 运行此代码 #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'; } 更重要的是,当一个函数同时具有右值引用和左值引用重载时,右值引用重载绑定到右值(包括纯右值和消亡值),而左值引用重载绑定到左值 运行此代码 #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())。 由于右值引用可以绑定到消亡值,因此它们可以引用非临时对象 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
请注意,右值引用和常量左值引用会延长临时对象的生命周期(有关规则和例外,请参见引用初始化)。
如果被引用的对象已被销毁(例如,通过显式析构函数调用),但存储空间未被解除分配,则在有限的情况下可以使用对超出生命周期的对象的引用,并且如果对象在同一存储空间中重新创建,则可能变为有效(有关详细信息,请参见超出生命周期的访问)。
[编辑] 类型不可访问的引用
尝试将引用绑定到对象,其中转换后的初始化器是左值(在 C++11 之前)泛左值(自 C++11 起),通过该泛左值,对象不是类型可访问的,会导致未定义的行为
char x alignas(int); int& ir = *reinterpret_cast<int*>(&x); // undefined behavior: // initializer refers to char object
[编辑] 调用不兼容的引用
尝试将引用绑定到函数,其中转换后的初始化器是左值(在 C++11 之前)泛左值(自 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++ 标准。
DR | 应用于 | 已发布行为 | 正确行为 |
---|---|---|---|
CWG 453 | C++98 | 不清楚引用不能绑定到哪个对象或函数 | 已明确 |
CWG 1510 | C++11 | cv 限定的引用不能在 decltype 的操作数中形成 | 允许 |
CWG 2550 | C++98 | 形参可以具有“引用到 void” 类型 | 不允许 |
CWG 2933 | C++98 | 访问悬垂引用的行为不明确 | 已明确 |
[编辑] 外部链接
Thomas Becker, 2013 - C++ 右值引用详解 |