命名空间
变体
操作

引用初始化

来自 cppreference.com
< cpp‎ | language
 
 
C++语言
 
 

将引用绑定到对象。

内容

[编辑] 语法

[编辑] 非列表初始化
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 语句中,当函数返回引用类型时。 如果返回的引用绑定到 临时表达式 的结果,则程序格式错误。(自 C++26 起)
5) 当一个 非静态数据成员 的引用类型使用 成员初始化器 初始化时。

[编辑] 解释

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

[编辑] 定义

对于两个类型 T1T2

  • 给定 T1T2 的 cv 无限定版本分别为 U1U2,如果 U1U2 相似,或者 U1U2基类,那么 T1T2引用相关的
  • 如果“指向 T2 的指针”类型的右值可以通过标准转换序列转换为“指向 T1 的指针”类型,那么 T1T2引用兼容的

[编辑] 初始化规则

如果引用初始化使用普通的或指定的(自 C++20 起)列表初始化,则遵循 列表初始化 的规则。

(自 C++11 起)

对于非列表引用初始化,给定 target 的类型为 U,该引用要么直接绑定target,要么绑定到从 target 转换而来类型为 T 的值。首先考虑直接绑定,然后考虑间接绑定,如果两种绑定都不可用,则程序格式错误。

在所有使用两种类型的引用兼容关系来确定引用绑定有效性且标准转换序列将格式错误的情况下,需要这种绑定的程序格式错误。

[编辑] 直接绑定

如果满足以下所有条件

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

那么引用绑定到 target,或者绑定到其合适的基类子对象

double d = 2.0;
double& rd = d;        // rd refers to d
const double& rcd = d; // rcd refers to d
 
struct A {};
struct B : A {} b;
 
A& ra = b;             // ra refers to A subobject in b
const A& rca = b;      // rca refers to A subobject in b

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

  • 要初始化的引用是左值引用。
  • U 是一个类类型。
  • TU 不相关。
  • target 可以转换为类型为 V 的左值,使得 TV 引用兼容。

那么引用绑定到转换结果的左值,或者绑定到其合适的基类子对象

struct A {};
struct B : A { operator int&(); };
 
int& ir = B(); // ir refers to the result of B::operator int&

否则,如果要初始化的引用是左值引用,并且 T 没有被限定为 const 或被限定为 volatile,则程序格式错误

double& rd2 = 2.0; // error: not an lvalue and reference is not const
int i = 2;
double& rd3 = i;   // error: type mismatch and reference is not const

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

  • target 是以下任何类别的值
  • 右值
(直到 C++11)
  • 非位域 x 值
  • 类右值
  • 数组右值
  • 函数左值
(自 C++11 起)
(直到 C++17)
  • 非位域右值
  • 函数左值
(自 C++17 起)
  • TU 是引用兼容的。

那么引用绑定到 target,或者绑定到其合适的基类子对象

struct A {};
struct B : A {};
extern B f();
 
const A& rca2 = f(); // bound to the A subobject of the B rvalue.
A&& rra = f();       // same as above
 
int i2 = 42;
int&& rri = static_cast<int&&>(i2); // bound directly to i2

如果 target 是一个右值,则会对其应用 临时物质化,将右值的类型视为调整后的类型 P

  • P 是从 target 的类型(即 U调整而来,通过添加 T 的 cv 限定到它。

在这种情况下,引用绑定到结果对象,或者绑定到其合适的基类子对象。

(自 C++17 起)

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

  • U 是一个类类型。
  • TU 不相关。
  • target 可以转换为类型为 V 的值 v,使得 TV 引用兼容,其中 v 属于以下任何类别
  • 右值
(直到 C++11)
  • x 值
  • 类右值
  • 函数左值
(自 C++11 起)
(直到 C++17)
  • 右值
  • 函数左值
(自 C++17 起)

那么引用绑定到转换结果,或者绑定到其合适的基类子对象

struct A {};
struct B : A {};
struct X { operator B(); } x;
 
const A& r = x; // bound to the A subobject of the result of the conversion
B&& rrb = x;    // bound directly to the result of the conversion

如果转换结果是右值,则会对其应用 临时物质化,将右值的类型视为调整后的类型 P

  • P 是从转换结果类型调整而来,通过添加 T 的 cv 限定到它。

在这种情况下,引用绑定到结果对象,或者绑定到其合适的基类子对象。

(自 C++17 起)

[编辑] 间接绑定

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

如果 TU 是类类型,则使用 复制初始化 对象的规则,通过用户定义的转换来考虑用户定义的转换。如果相应的非引用复制初始化将格式错误,则程序格式错误。然后使用转换函数调用的结果(如非引用 复制初始化 所述)来直接初始化引用。对于这种直接初始化,不考虑用户定义的转换。

否则,会创建一个类型为 T 的临时变量,并使用 target 对其进行复制初始化。然后将引用绑定到该临时变量。

(直到 C++17)

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

(自 C++17 起)
const std::string& rs = "abc"; // rs refers to temporary copy-initialized from char array
const double& rcd2 = 2;        // rcd2 refers to temporary with value 2.0
int i3 = 2;
double&& rrd3 = i3;            // rrd3 refers to temporary with value 2.0

[编辑] 临时变量的生命周期

每当引用绑定到临时对象或其子对象时,临时对象的生命周期都会扩展以匹配引用的生命周期(检查 临时对象生命周期异常),其中临时对象或其子对象由以下表达式之一表示

  • 一个 右值 表达式,其类型为对象类型,
(直到 C++17)
(自 C++17 起)
  • 一个括号表达式 (e),其中 e 是这些表达式之一,
  • 一个 内置下标表达式,其形式为 a[n]n[a],其中 a 是一个数组,并且是这些表达式之一,
  • 一个 类成员访问表达式,其形式为 e.m,其中 e 是这些表达式之一,并且 m 指定了对象类型的一个非静态数据成员,
  • 一个 指向成员的操作,其形式为 e.*mp,其中 e 是这些表达式之一,并且 mp 是一个指向数据成员的指针,
  • 一个 const_caststatic_castdynamic_castreinterpret_cast 转换,它没有用户定义的转换,将这些表达式之一转换为左值引用,它引用操作数所指定的对象,或者引用其完整对象或其子对象(显式转换 表达式被解释为这些转换的序列),
  • 一个 条件表达式,其形式为 cond ? e1 : e2,它是左值,其中 e1e2 是这些表达式之一,或者
  • 一个 内置逗号表达式,其形式为 x, e,它是左值,其中 e 是这些表达式之一。

此生命周期规则有以下例外

  • 绑定到函数的 return 语句中的返回值的临时变量不会扩展:它会在返回表达式结束时立即销毁。这种 return 语句总是返回一个悬空引用。
(直到 C++26)
  • 绑定到函数调用中引用参数的临时变量会存在到包含该函数调用的完整表达式结束为止:如果函数返回一个引用,该引用比完整表达式存活更久,则它会变成一个悬空引用。
  • 绑定到 new 表达式中使用的初始化器中的引用的临时变量会存在到包含该 new 表达式的完整表达式结束为止,而不是像初始化对象那样长。如果初始化对象比完整表达式存活更久,则它的引用成员会变成一个悬空引用。
(自 C++11 起)
  • 使用直接初始化语法(圆括号) 初始化的聚合的引用元素中的引用绑定的临时变量会存在到包含初始化器的完整表达式结束为止,而不是像使用列表初始化语法{花括号} 一样。
struct A
{
    int&& r;
};
 
A a1{7}; // OK, lifetime is extended
A a2(7); // well-formed, but dangling reference
(自 C++20 起)

一般来说,临时对象的生存期不能通过“传递”来进一步延长:从绑定到临时对象的引用变量或数据成员初始化的第二个引用不会影响其生存期。

[编辑] 注释

引用仅在函数参数声明、函数返回类型声明、类成员声明以及使用extern 规范符时才会没有初始化器。

在解决CWG 问题 1696 之前,允许临时对象绑定到构造函数初始化列表中的引用成员,并且它只在构造函数退出之前持续存在,而不是与对象的生命周期一样长。自CWG 1696以来,这种初始化是非法的,尽管许多编译器仍然支持它(clang 是一个显著的例外)。

[编辑] 示例

#include <sstream>
#include <utility>
 
struct S
{
    int mi;
    const std::pair<int, int>& mp; // reference member
};
 
void foo(int) {}
 
struct A {};
 
struct B : A
{
    int n;
    operator int&() { return n; }
};
 
B bar() { return B(); }
 
//int& bad_r;      // error: no initializer
extern int& ext_r; // OK
 
int main()
{
//  Lvalues
    int n = 1;
    int& r1 = n;                    // lvalue reference to the object n
    const int& cr(n);               // reference can be more cv-qualified
    volatile int& cv{n};            // any initializer syntax can be used
    int& r2 = r1;                   // another lvalue reference to the object n
//  int& bad = cr;                  // error: less cv-qualified
    int& r3 = const_cast<int&>(cr); // const_cast is needed
 
    void (&rf)(int) = foo; // lvalue reference to function
    int ar[3];
    int (&ra)[3] = ar;     // lvalue reference to array
 
    B b;
    A& base_ref = b;        // reference to base subobject
    int& converted_ref = b; // reference to the result of a conversion
 
//  Rvalues
//  int& bad = 1;        // error: cannot bind lvalue ref to rvalue
    const int& cref = 1; // bound to rvalue
    int&& rref = 1;      // bound to rvalue
 
    const A& cref2 = bar(); // reference to A subobject of B temporary
    A&& rref2 = bar();      // same
 
    int&& xref = static_cast<int&&>(n); // bind directly to n
//  int&& copy_ref = n;                 // error: can't bind to an lvalue
    double&& copy_ref = n;              // bind to an rvalue temporary with value 1.0
 
//  Restrictions on temporary lifetimes
//  std::ostream& buf_ref = std::ostringstream() << 'a';
                     // the ostringstream temporary was bound to the left operand
                     // of operator<< but its lifetime ended at the semicolon so
                     // the buf_ref is a dangling reference
 
    S a {1, {2, 3}}; // temporary pair {2, 3} bound to the reference member
                     // a.mp and its lifetime is extended to match 
                     // the lifetime of object a
 
    S* p = new S{1, {2, 3}}; // temporary pair {2, 3} bound to the reference
                             // member p->mp, but its lifetime ended at the semicolon
                             // p->mp is a dangling reference
    delete p;
 
    // Imitate [[maybe_unused]] applied to the following variables:
    [](...){}
    (
        cv, r2, r3, rf, ra, base_ref, converted_ref,
        a, cref, rref, cref2, rref2, copy_ref, xref
    );
}

[编辑] 缺陷报告

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

DR 应用于 发布的行为 正确行为
CWG 391 C++98 用类类型初始化对 const 限定类型的引用
右值可能会创建一个临时对象,并且需要该类的构造函数
才能将右值复制到该临时对象中
不会创建临时对象,
不需要构造函数
CWG 450 C++98 无法用与引用兼容的数组右值初始化对 const 限定数组的引用
允许
CWG 589 C++98 引用不能直接绑定到数组或类右值
C++98 CWG 656
用与引用不兼容但具有转换为与引用兼容类型的转换函数的类型初始化对 const 限定类型的引用会绑定到从转换函数的返回值(或其基类子对象)复制的临时对象
直接绑定到返回值(或其基类
子对象)

CWG 1287
C++11
从类类型的目标到另一个与引用兼容的类型的转换只能是隐式的 允许显式 转换
CWG 1295
引用可以绑定到位域 x 值
禁止 允许显式 CWG 1299
临时对象的定义不清楚 C++98 已明确
CWG 1571 C++98 间接绑定中的用户定义转换没有考虑目标的类型
已考虑
CWG 1604 C++98 间接绑定中没有考虑用户定义转换
C++98 CWG 2352
引用兼容性没有考虑限定符转换 CWG 2481
C++17
cv 限定符没有添加到间接绑定中临时对象物化的结果类型中
已添加 CWG 2481
cv 限定符没有添加到间接绑定中临时对象物化的结果类型中
CWG 2657 C++98 直接绑定中临时对象物化的 CWG 1299

列表初始化