Namespaces
Variants

Reference initialization

From cppreference.net
C++ language
General topics
Flow control
Conditional execution statements
Iteration statements (loops)
Jump statements
Functions
Function declaration
Lambda function expression
inline specifier
Dynamic exception specifications ( until C++17* )
noexcept specifier (C++11)
Exceptions
Namespaces
Types
Specifiers
constexpr (C++11)
consteval (C++20)
constinit (C++20)
Storage duration specifiers
Initialization
Expressions
Alternative representations
Literals
Boolean - Integer - Floating-point
Character - String - nullptr (C++11)
User-defined (C++11)
Utilities
Attributes (C++11)
Types
typedef declaration
Type alias declaration (C++11)
Casts
Memory allocation
Classes
Class-specific function properties
Special member functions
Templates
Miscellaneous

将引用绑定到对象。

目录

语法

非列表初始化
T  & ref = target ;

T  & ref ( target );

(1)
T  && ref = target ;

T  && ref ( target );

(2) (自 C++11 起)
func-refpar ( target ) (3)
return target ; (4) (在 func-refret  的定义内)
Class :: Class ( ... ) : ref-member ( target ) { ... } (5) (在 Class  的定义内)
普通列表初始化 (始于 C++11)
T  & ref = { arg1 , arg2 , ... };

T  & ref { arg1 , arg2 , ... };

(1)
T  && ref = { arg1 , arg2 , ... };

T  && ref { arg1 , arg2 , ... };

(2)
func-refpar ({ arg1 , arg2 , ... }); (3)
指定列表初始化 (C++20 起)
T  & ref = {. des1 = arg1 , . des2 { arg2 } ... };

T  & ref {. des1 = arg1 , . des2 { arg2 } ... };

(1)
T  && ref = {. des1 = arg1 , . des2 { arg2 } ... };

T  && ref {. des1 = arg1 , . des2 { arg2 } ... };

(2)
func-refpar ({. des1 = arg1 , . des2 { arg2 } ... }); (3)

T 的引用可以用 T 类型的对象、 T 类型的函数,或可隐式转换为 T 的对象进行初始化。一旦初始化后,引用不能被重新绑定(更改)以指向其他对象。

引用在以下情况下被初始化:

1) 当具有初始化器的命名 左值引用 变量被声明时。
2) 当具有初始化器的具名 右值引用 变量被声明时。
3) 在函数调用表达式中,当函数参数具有引用类型时。
4) return 语句中,当函数返回引用类型时。 若返回的引用绑定到 临时表达式 的结果,则程序非良构。 (since C++26)
5) 当使用 成员初始化器 初始化引用类型的 非静态数据成员 时。

说明

T - 被引用的类型
ref - 待初始化的引用变量
target - 用作初始化器的表达式
func-refpar - 具有引用类型参数的函数( T  & T  && (C++11 起)
func-refret - 返回类型为引用类型的函数( T  & T  && (C++11 起)
Class - 类名
ref-member - Class 中引用类型的非静态数据成员( T  & T  && (C++11 起)
des1 , des2 , ... - 指示符
arg1 , arg2 , ... - 初始化器列表中的初始化器

定义

对于两种类型 T1 T2

  • 给定 T1 T2 的 cv 非限定版本分别为 U1 U2 ,若 U1 U2 相似 ,或 U1 U2 基类 ,则称 T1 T2 引用相关
  • 若类型“指向 T2 的指针”的纯右值可通过标准转换序列转换为类型“指向 T1 的指针”,则称 T1 T2 引用兼容

初始化规则

若引用初始化使用普通 或指定 (始于 C++20) 列表初始化,则遵循 列表初始化 规则。

(始于 C++11)

对于非列表引用初始化,给定 目标 的类型为 U ,该引用要么 直接绑定 到目标,要么绑定到从 目标 转换而来的类型 T 的值。首先考虑直接绑定,其次考虑间接绑定,如果两种绑定方式均不可用,则程序非良构。

在所有使用两种类型的引用兼容关系来确定引用绑定有效性,且标准转换序列将不合规的情况下,需要此类绑定的程序是不合规的。

直接绑定

若满足以下所有条件:

  • 要初始化的引用是一个左值引用。
  • target 是一个非 位域 的左值。
  • T U 是引用兼容的。

随后引用将绑定到 target ,或绑定到其对应的基类子对象:

double d = 2.0;
double& rd = d;        // rd 引用 d
const double& rcd = d; // rcd 引用 d
struct A {};
struct B : A {} b;
A& ra = b;             // ra 引用 b 中的 A 子对象
const A& rca = b;      // rca 引用 b 中的 A 子对象

否则,如果满足以下所有条件:

  • 待初始化的引用是一个左值引用。
  • U 是一个类类型。
  • T U 不存在引用关联。
  • target 可转换为类型 V 的左值,且 T V 引用兼容。

然后引用绑定到转换结果的左值,或其适当的基类子对象:

struct A {};
struct B : A { operator int&(); };
int& ir = B(); // ir 引用 B::operator int& 的返回结果

否则,如果要初始化的引用是左值引用,且 T 不是 const 限定或具有 volatile 限定,则程序非良构:

double& rd2 = 2.0; // 错误:不是左值且引用不是常量
int i = 2;
double& rd3 = i;   // 错误:类型不匹配且引用不是常量

否则,如果满足以下所有条件:

  • target 属于以下任一类别:
  • 右值
(C++11 前)
  • 非位域 xvalue
  • 类 prvalue
  • 数组 prvalue
  • 函数左值
(C++11 起)
(C++17 前)
  • 非位域右值
  • 函数左值
(C++17 起)
  • T U 引用兼容。

随后引用绑定到 target ,或其对应的基类子对象:

struct A {};
struct B : A {};
extern B f();
const A& rca2 = f(); // 绑定到B右值的A子对象
A&& rra = f();       // 同上
int i2 = 42;
int&& rri = static_cast<int&&>(i2); // 直接绑定到i2

target 为纯右值,则对其应用 临时物化 ,并将该纯右值的类型视为调整后的类型 P

  • P target 的类型(即 U )经 调整 而得,具体方式为将 T 的 cv 限定符添加到该类型上。

在此情况下,该引用将绑定到结果对象,或其适当的基类子对象。

(since C++17)

否则,如果满足以下所有条件:

  • U 是一个类类型。
  • T U 无引用关联。
  • target 可转换为类型 V 的值 v ,使得 T V 引用兼容,其中 v 属于以下任意类别:
  • 右值
(C++11 前)
  • 亡值
  • 类纯右值
  • 函数左值
(C++11 起)
(C++17 前)
  • 右值
  • 函数左值
(C++17 起)

随后引用将绑定到转换结果,或绑定到其适当的基类子对象:

struct A {};
struct B : A {};
struct X { operator B(); } x;
const A& r = x; // 绑定到转换结果的A子对象
B&& rrb = x;    // 直接绑定到转换结果

若转换结果为纯右值,则对其应用 临时物化 ,并将该纯右值的类型视为调整后的类型 P

  • P 由转换结果的类型通过添加 T 的 cv 限定符进行 调整

在此情况下,引用将绑定到结果对象,或其适当的基类子对象。

(since C++17)

间接绑定

如果直接绑定不可用,则会考虑间接绑定。在这种情况下, T 不能与 U 形成引用关联。

如果 T U 是类类型,则按照通过用户定义转换对 T 类型对象进行 复制初始化 的规则来考虑用户定义转换。如果对应的非引用复制初始化将导致程序非良构,则此程序非良构。随后,如针对非引用 复制初始化 所述,调用转换函数的结果将用于直接初始化该引用。在此直接初始化过程中,不再考虑用户定义转换。

否则,将创建类型为 T 的临时对象并从 目标 复制初始化。随后将该引用绑定至此临时对象。

(C++17 前)

否则, 目标 将隐式转换为“cv 无限定 T ”类型的纯右值。应用临时物化转换(将纯右值的类型视为 T ),并将引用绑定到结果对象。

(C++17 起)
const std::string& rs = "abc"; // rs 引用从字符数组临时拷贝初始化的对象
const double& rcd2 = 2;        // rcd2 引用值为 2.0 的临时对象
int i3 = 2;
double&& rrd3 = i3;            // rrd3 引用值为 2.0 的临时对象

临时对象的生命周期

当引用被绑定到临时对象或其子对象时,该临时对象的生存期将被延长以匹配引用的生存期(查看 临时对象生存期例外情况 ),其中临时对象或其子对象由以下表达式之一表示:

(C++17 前)
(C++17 起)

此生命周期规则存在以下例外情况:

  • return 语句中绑定到函数返回值的临时对象不会延长生命周期:它会在返回表达式结束时立即被销毁。此类 return 语句总是返回一个悬垂引用。
(until C++26)
  • 函数调用中绑定到引用参数的临时对象,其生命周期持续到包含该函数调用的完整表达式结束为止:如果函数返回一个引用,且该引用的生存期超过完整表达式,则它将变成一个悬垂引用。
  • 在 new 表达式的初始化器中绑定到引用的临时变量,其生命周期持续到包含该 new 表达式的完整表达式结束为止,而非与初始化对象存在的时间相同。如果初始化对象的生存期超过完整表达式,其引用成员将变成悬垂引用。
(since C++11)
  • 在使用 直接初始化 语法 圆括号 进行聚合初始化时,绑定到引用元素的临时对象的生命周期将持续到包含初始化器的完整表达式结束,这与使用 列表初始化 语法 { 花括号 } 的情况不同。
struct A
{
    int&& r;
};
A a1{7}; // OK, lifetime is extended
A a2(7); // well-formed, but dangling reference
(since C++20)

通常来说,临时对象的生命周期无法通过"传递"进一步延长:从绑定该临时对象的引用变量或数据成员初始化的第二个引用,不会影响其生命周期。

注释

引用仅在函数参数声明、函数返回类型声明、类成员声明以及带有 extern 说明符时可以不包含初始化式。

CWG issue 1696 解决之前,允许在构造函数 初始化列表 中将临时对象绑定到引用成员,且该临时对象仅持续到构造函数退出为止,而非与对象生命周期一致。自 CWG 1696 起,此类初始化被视为非良构,尽管许多编译器仍支持该行为(clang 是显著例外)。

示例

#include <sstream>
#include <utility>
struct S
{
    int mi;
    const std::pair<int, int>& mp; // 引用成员
};
void foo(int) {}
struct A {};
struct B : A
{
    int n;
    operator int&() { return n; }
};
B bar() { return B(); }
//int& bad_r;      // 错误:无初始化器
extern int& ext_r; // 正确
int main()
{
//  左值
    int n = 1;
    int& r1 = n;                    // 指向对象 n 的左值引用
    const int& cr(n);               // 引用可具有更多 cv 限定
    volatile int& cv{n};            // 可使用任意初始化语法
    int& r2 = r1;                   // 指向对象 n 的另一个左值引用
//  int& bad = cr;                  // 错误:cv 限定更少
    int& r3 = const_cast<int&>(cr); // 需要 const_cast
    void (&rf)(int) = foo; // 函数左值引用
    int ar[3];
    int (&ra)[3] = ar;     // 数组左值引用
    B b;
    A& base_ref = b;        // 指向基类子对象的引用
    int& converted_ref = b; // 指向转换结果的引用
//  右值
//  int& bad = 1;        // 错误:无法将左值引用绑定到右值
    const int& cref = 1; // 绑定到右值
    int&& rref = 1;      // 绑定到右值
    const A& cref2 = bar(); // 指向 B 临时对象中 A 子对象的引用
    A&& rref2 = bar();      // 同上
    int&& xref = static_cast<int&&>(n); // 直接绑定到 n
//  int&& copy_ref = n;                 // 错误:无法绑定到左值
    double&& copy_ref = n;              // 绑定到值为 1.0 的右值临时对象
//  临时对象生命期限制
//  std::ostream& buf_ref = std::ostringstream() << 'a';
                     // ostringstream 临时对象已绑定到 operator<< 的左操作数
                     // 但其生命期在分号处结束,因此 buf_ref 成为悬垂引用
    S a {1, {2, 3}}; // 临时 pair {2, 3} 绑定到引用成员 a.mp
                     // 其生命期被延长以匹配对象 a 的生命期
    S* p = new S{1, {2, 3}}; // 临时 pair {2, 3} 绑定到引用成员 p->mp
                             // 但其生命期在分号处结束,p->mp 成为悬垂引用
    delete p;
    // 模拟应用于以下变量的 [[maybe_unused]]:
    [](...){}
    (
        cv, r2, r3, rf, ra, base_ref, converted_ref,
        a, cref, rref, cref2, rref2, copy_ref, xref
    );
}

缺陷报告

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

缺陷报告 适用标准 发布时行为 正确行为
CWG 391 C++98 用类类型右值初始化const限定类型的引用可能创建临时对象,且需要该类的构造函数将右值复制到临时对象中 不创建临时对象,不需要构造函数
CWG 450 C++98 无法用引用兼容的数组右值初始化const限定数组的引用 允许此操作
CWG 589 C++98 引用不能直接绑定到数组或类右值 允许此操作
CWG 656 C++98 用非引用兼容但具有转换函数的类型初始化const限定类型引用时,会绑定到从转换函数返回值(或其基类子对象)复制的临时对象 直接绑定到返回值(或其基类子对象)
CWG 1287 C++11 从类类型的 target 到其他引用兼容类型的转换只能是隐式的 允许显式转换
CWG 1295 C++11 引用可以绑定到位域xvalue 禁止此操作
CWG 1299 C++98 临时对象的定义不明确 已明确界定
CWG 1571 C++98 间接绑定中的用户定义转换未考虑 target 的类型 已考虑此因素
CWG 1604 C++98 间接绑定中未考虑用户定义转换 已考虑此因素
CWG 2352 C++98 引用兼容性未考虑限定符转换 已考虑此因素
CWG 2481 C++17 间接绑定中临时物化的结果类型未添加cv限定符 已添加
CWG 2657 C++17 直接绑定中临时物化的结果类型未添加cv限定符 已添加
CWG 2801 C++98 允许在间接绑定中使用引用相关类型 禁止此操作

另请参阅