Reference declaration
声明一个命名变量为引用,即对已存在对象或函数的别名。
| 目录 | 
语法
引用变量声明是任何 声明符 具有以下形式的简单声明
| 
          
           &
          
         attr
         
         
         
          (可选)
         
         
          declarator | (1) | ||||||||
| 
          
           &&
          
         attr
         
         
         
          (可选)
         
         
          declarator | (2) | (自 C++11 起) | |||||||
        D
       
       声明为对由
       
        声明说明符序列
       
       
        S
       
       确定的类型的
       
        左值引用
       
       。
      
        D
       
       声明为对由
       
        声明说明符序列
       
       
        S
       
       确定的类型的
       
        右值引用
       
       。
      | declarator | - | 除引用声明符、 数组声明符 和 指针声明符 外的任意 声明符 (不存在引用的引用、引用的数组或指向引用的指针) | 
| attr | - | (C++11 起) 属性 列表 | 
引用必须初始化为指向有效的对象或函数:参见 引用初始化 。
类型“(可能带有 cv 限定符的) void 引用”无法构成。
       引用类型不能在顶层进行
       
        cv限定
       
       ;声明中不存在相关语法,若对typedef名称
       
        
         或
         
          
           decltype
          
         
         说明符
        
        
         
          (C++11 起)
         
        
       
       ,或
       
        类型模板参数
       
       添加限定符,该限定符将被忽略。
      
引用不是对象;它们不一定占用存储空间,尽管编译器可能会在需要实现预期语义时分配存储空间(例如,引用类型的非静态数据成员通常会使类的大小增加存储内存地址所需的量)。
由于引用不是对象,因此不存在引用的数组、指向引用的指针以及引用的引用:
int& a[3]; // 错误 int&* p; // 错误 int& &r; // 错误
| 引用折叠允许通过模板或类型定义中的类型操作形成引用的引用,此时适用 引用折叠 规则:右值引用到右值引用折叠为右值引用,所有其他组合形成左值引用: 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"; // 修改 s // r2 += "!"; // 错误:无法通过常量引用修改 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() 返回字符的引用 } int main() { std::string str = "Test"; char_number(str, 1) = 'a'; // 函数调用是左值,可以被赋值 std::cout << str << '\n'; }
| 右值引用右值引用可用于 延长临时对象的生命周期 (注意,const左值引用也可以延长临时对象的生命周期,但无法通过它们修改对象): 
            运行此代码
            #include <iostream> #include <string> int main() { std::string s1 = "Test"; // std::string&& r1 = s1; // 错误:无法绑定到左值 const std::string& r2 = s1 + s1; // 正确:const左值引用延长生命周期 // r2 += "Test"; // 错误:无法通过const引用修改 std::string&& r3 = s1 + s1; // 正确:右值引用延长生命周期 r3 += "Test"; // 正确:可通过非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); // 调用 f(int&) f(ci); // 调用 f(const int&) f(3); // 调用 f(int&&) // 如果未提供 f(int&&) 重载,则会调用 f(const int&) f(std::move(i)); // 调用 f(int&&) // 右值引用变量在表达式中使用时是左值 int&& x = 1; f(x); // 调用 f(int& x) f(std::move(x)); // 调用 f(int&& x) } 这使得 移动构造函数 、 移动赋值 运算符和其他支持移动操作的函数(例如 std::vector::push_back() )在适用时能够自动被选择。 由于右值引用可以绑定到亡值,它们可以引用非临时对象: int i2 = 42; int&& rri = std::move(i2); // 直接绑定到 i2 这使得可以从作用域内不再需要的对象中移出内容: std::vector<int> v{1, 2, 3, 4, 5}; std::vector<int> v2(std::move(v)); // 将右值引用绑定到 v assert(v.empty()); 转发引用转发引用是一种特殊的引用,能够保留函数参数的值类别,使其可以通过 std::forward 进行 转发 。转发引用是以下两种情况之一: 
          
           1)
          
          函数模板中声明为右值引用的函数形参,且其类型为该同一函数模板的cv非限定
          
           类型模板参数
          
          :
          template<class T> int f(T&& x) // x 是转发引用 { return g(std::forward<T>(x)); // 因此可以被转发 } int main() { int i; f(i); // 实参是左值,调用 f<int&>(int&),std::forward<int&>(x) 是左值 f(0); // 实参是右值,调用 f<int>(int&&),std::forward<int>(x) 是右值 } template<class T> int g(const T&& x); // x 不是转发引用:const T 不是 cv 非限定 template<class T> struct A { template<class U> A(T&& x, U&& y, int* p); // x 不是转发引用:T 不是构造函数的 // 类型模板参数, // 但 y 是转发引用 }; 
          
           2)
          
          
           
            
             auto
            
            <span class="sy3
           
          
          | 
悬垂引用
尽管引用在初始化时始终指向有效的对象或函数,但仍可能创建出被引用对象的 生命周期 已结束但引用仍可访问的程序( 悬空引用 )。
给定一个引用类型的表达式 expr ,并令 target 为该引用所指向的对象或函数:
- 如果在 expr 的求值上下文中,指向 target 的指针是 有效的 ,则结果将指向 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); // 未定义行为: // 初始化器引用字符对象
调用不兼容引用
尝试将引用绑定到函数时,若转换后的初始化器是 左值 (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 | 访问悬空引用的行为不明确 | 已明确 | 
外部链接
| 托马斯·贝克尔,2013 - C++右值引用详解 |