引用声明
声明具名变量为引用,即既存对象或函数的别名。
语法
引用变量声明是拥有下列形式的被简单声明的声明符:
& 属性 (可选) 声明符
|
(1) | ||||||||
&& 属性 (可选) 声明符
|
(2) | (C++11 起) | |||||||
S& D; 是将 D 声明为声明说明符序列 S 所确定的类型的左值引用。S&& D; 是将 D 声明为声明说明符序列 S 所确定的类型的右值引用。| 声明符 | - | 除引用声明符之外的任何其他声明符(不存在引用的引用) |
| 属性 | - | (C++11 起) 属性的列表 |
引用必须被初始化为指代一个有效的对象或函数:见引用初始化。
无法组成“到(可有 cv 限定的)void 的引用”类型。
引用类型不能在顶层有 cv 限定;声明中没有为此而设的语法,如果在 typedef 名、decltype 说明符(C++11 起)或类型模板形参上添加了该限定符,它将会被忽略。
引用不是对象;它们不必占用存储,尽管编译器会在需要实现所需语义(例如,引用类型的非静态数据成员通常会增加类的大小,量为存储内存地址所需)的情况下分配存储。
因为引用不是对象,所以不存在引用的数组,不存在指向引用的指针,不存在引用的引用:
int& a[3]; // 错误
int&* p; // 错误
int& &r; // 错误
引用折叠通过模板或 typedef 中的类型操作可以构成引用的引用,此时适用引用折叠 规则:右值引用的右值引用折叠成右值引用,所有其他组合均折叠成左值引用: (这条规则,和将 |
(C++11 起) |
左值引用
左值引用可用于建立既存对象的别名(可拥有不同的 cv 限定):
#include <iostream>
#include <string>
int main()
{
std::string s = "Ex";
std::string& r1 = s;
const std::string& r2 = s;
r1 += "ample"; // 修改 s
// r2 += "!"; // 错误:不能通过到 const 的引用修改
std::cout << r2 << '\n'; // 打印 s,它现在保有 "Example"
}
它们也能用于在函数调用中实现按引用传递语义:
#include <iostream>
#include <string>
void double_string(std::string& s)
{
s += s; // 's' 与 main() 的 '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() 返回 char 的引用
}
int main()
{
std::string str = "Test";
char_number(str, 1) = 'a'; // 函数调用是左值,可被赋值
std::cout << str << '\n';
}
右值引用右值引用可用于为临时对象延长生存期(注意,到 const 的左值引用也能延长临时对象生存期,但这些对象无法被修改): 运行此代码 更重要的是,当函数同时具有右值引用和左值引用的重载时,右值引用重载绑定到右值(包含纯右值和亡值),而左值引用重载绑定到左值: 运行此代码 这允许在适当时机自动选择移动构造函数、移动赋值运算符和其他具有移动能力的函数(例如 std::vector::push_back())。 因为右值引用能绑定到亡值,所以它们能指代非临时对象: 这使得作用域中不再需要的对象可以被移动出去: 转发引用转发引用是一种特殊的引用,它保持函数实参的值类别,使得 std::forward 能用来转发实参。转发引用是下列之一: 1) 函数模板的函数形参,其被声明为同一函数模板的类型模板形参的无 cv 限定的右值引用:
参阅模板实参推导和 std::forward。 |
(C++11 起) |
悬垂引用
尽管引用一旦初始化就始终指代一个有效的对象或函数,但有可能创建一个程序,其中被指代对象的生存期结束而引用仍保持可访问(悬垂)。
给定具有引用类型的表达式 expr,并设 target 为该引用指代的对象或函数:
- 如果指向
target的指针在求值expr的语境中有效,那么结果指代target。 - 否则行为未定义。
std::string& f()
{
std::string s = "Example";
return s; // 退出 s 的作用域:调用其析构函数并解分配其存储
}
std::string& r = f(); // 悬垂引用
std::cout << r; // 未定义行为:从悬垂引用读取
std::string s = f(); // 未定义行为:从悬垂引用复制初始化
注意,右值引用和到 const 的左值引用能延长临时对象的生存期(参阅引用初始化中的规则和例外情况)。
如果被指代对象被销毁(例如通过显式的析构函数调用),但存储尚未被解分配,则到生存期外的对象的引用仍能以有限的方式使用,且当在同一存储中重新创建对象时也可以变为有效(细节见在生存期之外进行访问)。
类型不可访问的引用
在转换后的初始化器是通过不具有类型可访问性的左值(C++11 前)泛左值(C++11 起)的对象的情况下,试图将引用绑定到该对象会导致未定义行为:
char x alignas(int);
int& ir = *reinterpret_cast<int*>(&x); // 未定义行为:初始化器指代 char 对象
调用不兼容的引用
在转换后的初始化器是函数左值(C++11 前)泛左值(C++11 起),并且该值的类型与函数定义的类型不具有调用兼容性的情况下,试图将引用绑定到该函数会导致未定义行为:
void f(int);
using F = void(float);
F& ir = *reinterpret_cast<F*>(&f); // 未定义行为:初始化器指代 void(int) 函数
注解
| 功能特性测试宏 | 值 | 标准 | 功能特性 |
|---|---|---|---|
__cpp_rvalue_references |
200610L |
(C++11) | 右值引用 |
缺陷报告
下列更改行为的缺陷报告追溯地应用于以前出版的 C++ 标准。
| 缺陷报告 | 应用于 | 出版时的行为 | 正确行为 |
|---|---|---|---|
| CWG 453 | C++98 | 不明确引用不能绑定到哪些对象和函数 | 使之明确 |
| CWG 1510 | C++11 | 不能在 decltype 的操作数中形成有 cv 限定的引用
|
已允许 |
| CWG 2550 | C++98 | 形参的类型可以是“到 void 的引用”
|
已禁止 |
| CWG 2933 | C++98 | 访问悬垂引用的行为不明确 | 使之明确 |
外部链接
| Thomas Becker, 2013 - C++ Rvalue References Explained |