引用初始化
将引用绑定到对象。
内容 |
[编辑] 语法
[编辑] 非列表初始化
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
是类类型,则使用 复制初始化 对象的规则,通过用户定义的转换来考虑用户定义的转换。如果相应的非引用复制初始化将格式错误,则程序格式错误。然后使用转换函数调用的结果(如非引用 复制初始化 所述)来直接初始化引用。对于这种直接初始化,不考虑用户定义的转换。
否则,会创建一个类型为 |
(直到 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
转换,它没有用户定义的转换,将这些表达式之一转换为左值引用,它引用操作数所指定的对象,或者引用其完整对象或其子对象(显式转换 表达式被解释为这些转换的序列), - 一个 条件表达式,其形式为 cond ? e1 : e2,它是左值,其中 e1 或 e2 是这些表达式之一,或者
- 一个 内置逗号表达式,其形式为 x, e,它是左值,其中 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 | 引用不能直接绑定到数组或类右值 | 。 |
。 | 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 |