引用初始化
将引用绑定到对象。
内容 |
[编辑] 语法
[编辑] 非列表初始化
T & ref = target ; T |
(1) | ||||||||
T && ref = target ; T |
(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 |
(1) | ||||||||
T && ref = { arg1, arg2, ... }; T |
(2) | ||||||||
func-refpar ({ arg1, arg2, ... }); |
(3) | ||||||||
[编辑] 指定列表初始化 (自 C++20 起)
T & ref = {. des1 = arg1 , . des2 { arg2 } ... }; T |
(1) | ||||||||
T && ref = {. des1 = arg1 , . des2 { arg2 } ... }; T |
(2) | ||||||||
func-refpar ({. des1 = arg1 , . des2 { arg2 } ... }); |
(3) | ||||||||
类型为 T
的引用可以使用类型为 T
的对象、类型为 T
的函数或隐式可转换为 T
的对象进行初始化。 一旦初始化,引用就不能重新定位(更改)以引用另一个对象。
引用在以下情况下初始化
[编辑] 解释
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, ... | - | 初始化列表中的初始化器 |
[编辑] 定义
对于两种类型 T1
和 T2
- 给定
T1
和T2
的 cv 限定符非限定版本分别为U1
和U2
,如果U1
与U2
相似,或者U1
是U2
的基类,则T1
与T2
是引用相关的。 - 如果类型为 “指向
T2
的指针” 的纯右值可以通过标准转换序列转换为类型 “指向T1
的指针”,则T1
与T2
是引用兼容的。
[编辑] 初始化规则
如果引用初始化使用普通或指定(自 C++20 起)列表初始化,则遵循列表初始化的规则。 |
(自 C++11 起) |
对于非列表引用初始化,给定 target 的类型为 U
,引用要么直接绑定到 target,要么绑定到从 target 转换而来的 T
类型的值。 首先考虑直接绑定,然后是间接绑定,如果两种绑定都不可用,则程序是非良构的。
在所有使用两种类型的引用兼容关系来建立引用绑定的有效性的情况下,如果标准转换序列是非良构的,则需要这种绑定的程序是非良构的。
[编辑] 直接绑定
如果满足以下所有条件
- 要初始化的引用是左值引用。
- target 是非位域左值。
-
T
与U
是引用兼容的。
则引用绑定到 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
是类类型。 -
T
与U
不是引用相关的。 - target 可以转换为
V
类型的左值,使得T
与V
是引用兼容的。
则引用绑定到转换的左值结果,或绑定到其适当的基类子对象
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) |
|
(自 C++11 起) (直到 C++17) |
|
(自 C++17 起) |
-
T
与U
是引用兼容的。
则引用绑定到 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 是纯右值,则对其应用临时对象实体化,将纯右值的类型视为调整后的类型
在这种情况下,引用绑定到结果对象,或绑定到其适当的基类子对象。 |
(自 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; // bound to the A subobject of the result of the conversion B&& rrb = x; // bound directly to the result of the conversion
如果转换的结果是纯右值,则对其应用临时对象实体化,将纯右值的类型视为调整后的类型
在这种情况下,引用绑定到结果对象,或绑定到其适当的基类子对象。 |
(自 C++17 起) |
[编辑] 间接绑定
如果直接绑定不可用,则考虑间接绑定。 在这种情况下,T
不能与 U
引用相关。
如果 T
或 U
是类类型,则使用 复制初始化类型为 T
的对象通过用户定义转换的规则来考虑用户定义的转换。 如果相应的非引用复制初始化是非良构的,则程序是非良构的。 然后,调用转换函数的结果(如非引用复制初始化所述)用于直接初始化引用。 对于此直接初始化,不考虑用户定义的转换。
否则,将创建类型为 |
(直到 C++17) |
否则,target 隐式转换为类型为 “cv 非限定 |
(自 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_cast
、static_cast
、dynamic_cast
或reinterpret_cast
转换,而没有用户定义的转换到glvalue(显式转型表达式被解释为这些转型的序列), - 形式为 cond ? e1 : e2 的条件表达式,它是 glvalue,其中 e1 或 e2 是这些表达式之一,或者
- 形式为 x, e 的内置逗号表达式,它是 glvalue,其中 e 是这些表达式之一。
此生命周期规则有以下例外情况
|
(直到 C++26) |
- 绑定到函数调用中引用参数的临时对象存在到包含该函数调用的完整表达式的末尾:如果函数返回的引用比完整表达式寿命更长,它将变成悬空引用。
|
(自 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 | 引用可能无法直接绑定到数组或类右值 | 允许了 |
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 | 引用相关类型允许用于间接绑定 | 禁止 |