命名空间
变体
操作

引用声明

来自 cppreference.com
< cpp‎ | 语言
 
 
C++ 语言
一般主题
流程控制
条件执行语句
if
迭代语句(循环)
for
range-for (C++11)
跳转语句
函数
函数声明
Lambda 函数表达式
inline 指定符
动态异常规范 (直到 C++17*)
noexcept 指定符 (C++11)
异常
命名空间
类型
指定符
const/volatile
decltype (C++11)
auto (C++11)
constexpr (C++11)
consteval (C++20)
constinit (C++20)
存储持续时间指定符
初始化
表达式
替代表示
字面量
布尔型 - 整型 - 浮点型
字符型 - 字符串型 - nullptr (C++11)
用户定义 (C++11)
实用程序
属性 (C++11)
类型
typedef 声明
类型别名声明 (C++11)
强制转换
内存分配
特定于类的函数属性
explicit (C++11)
static

特殊成员函数
模板
其他
 
 

将命名变量声明为引用,即对已存在对象或函数的别名。

内容

[编辑] 语法

引用变量声明是任何简单声明,其 声明符 具有以下形式

& attr (可选) 声明符 (1)
&& attr (可选) 声明符 (2) (自 C++11 起)
1) 左值引用声明符:声明 S& D;D 声明为对由 decl-specifier-seq S 确定的类型的左值引用
2) 右值引用声明符:声明 S&& D;D 声明为对由 decl-specifier-seq S 确定的类型的右值引用
声明符 - 任何 声明符,除了另一个引用声明符(没有对引用的引用)
attr - (自 C++11 起) 属性 列表

引用需要初始化为引用有效对象或函数:参见 引用初始化

类型“对(可能为 cv 限定的)void 的引用”无法形成。

引用类型不能在顶层 cv 限定;在声明中没有这种语法,如果向 typedef-namedecltype 指定符添加限定符,(自 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&&

(这,连同模板参数推导中使用 T&& 时的一些特殊规则,共同构成了使 std::forward 成为可能的规则。)

(自 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';
}

更重要的是,当一个函数同时具有右值引用和左值引用 重载 时,右值引用重载绑定到右值(包括纯右值和将要移动的值),而左值引用重载绑定到左值。

#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
};
2) auto&&,除非从大括号括起来的初始化列表推断出来,或者,在进行 类模板参数推导 时代表类模板的模板参数(自 C++17 起)
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 起)

[编辑] 悬空引用

虽然引用一旦初始化就会始终引用有效的对象或函数,但可以创建一个程序,其中所引用对象的 生存期 结束,而引用仍然可以访问(悬空)。访问这种引用会导致未定义行为。一个常见的例子是函数返回对自动变量的引用。

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 之前)泛左值(自 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

[编辑] 备注

功能测试宏 Std 功能
__cpp_rvalue_references 200610L (C++11) 右值引用

[编辑] 缺陷报告

以下行为更改缺陷报告被追溯应用到先前发布的 C++ 标准。

DR 应用于 已发布的行为 正确行为
CWG 453 C++98 不清楚引用不能绑定到哪些对象或函数。 已明确说明。
CWG 1510 C++11 cv 限定的引用不能在 decltype 的操作数中形成。 已允许。
CWG 2550 C++98 参数可以具有类型“对 void 的引用”。 已禁止。

[编辑] 外部链接

  Thomas Becker,2013 - C++ 右值引用详解