Reference initialization
将引用绑定到对象。
目录 |
语法
非列表初始化
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 | - |
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 前) |
|
(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
为纯右值,则对其应用
临时物化
,并将该纯右值的类型视为调整后的类型
在此情况下,该引用将绑定到结果对象,或其适当的基类子对象。 |
(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; // 直接绑定到转换结果
|
若转换结果为纯右值,则对其应用
临时物化
,并将该纯右值的类型视为调整后的类型
在此情况下,引用将绑定到结果对象,或其适当的基类子对象。 |
(since C++17) |
间接绑定
如果直接绑定不可用,则会考虑间接绑定。在这种情况下,
T
不能与
U
形成引用关联。
如果
T
或
U
是类类型,则按照通过用户定义转换对
T
类型对象进行
复制初始化
的规则来考虑用户定义转换。如果对应的非引用复制初始化将导致程序非良构,则此程序非良构。随后,如针对非引用
复制初始化
所述,调用转换函数的结果将用于直接初始化该引用。在此直接初始化过程中,不再考虑用户定义转换。
|
否则,将创建类型为
|
(C++17 前) |
|
否则,
目标
将隐式转换为“cv 无限定
|
(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 起) |
- 括号表达式 ( 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 是这些表达式之一。
此生命周期规则存在以下例外情况:
|
(until C++26) |
- 函数调用中绑定到引用参数的临时对象,其生命周期持续到包含该函数调用的完整表达式结束为止:如果函数返回一个引用,且该引用的生存期超过完整表达式,则它将变成一个悬垂引用。
|
(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 | 允许在间接绑定中使用引用相关类型 | 禁止此操作 |