引用初始化

来自cppreference.com
< cpp‎ | language


 
 
C++ 语言
 
 

将一个引用绑定到一个对象。

语法

非列表初始化
T & 引用 = 目标 ;

T & 引用 ( 目标 );

(1)
T && 引用 = 目标 ;

T && 引用 ( 目标 );

(2) (C++11 起)
接受引用的函数 ( 目标 ) (3)
return 目标 ; (4) (在返回引用的函数 的定义中)
::(...) : 引用成员 ( 目标 ) { ... } (5) (在 的定义中)
一般列表初始化 (C++11 起)
T & 引用 = { 实参1, 实参2, ... };

T & 引用 { 实参1, 实参2, ... };

(1)
T && 引用 = { 实参1, 实参2, ... };

T && 引用 { 实参1, 实参2, ... };

(2)
接受引用的函数 ({ 实参1, 实参2, ... }); (3)
指派列表初始化 (C++20 起)
T & 引用 = {.指派符1 = 实参1 , .指派符2 { 实参2 } ... };

T & 引用 {.指派符1 = 实参1 , .指派符2 { 实参2 } ... };

(1)
T && 引用 = {.指派符1 = 实参1 , .指派符2 { 实参2 } ... };

T && 引用 {.指派符1 = 实参1 , .指派符2 { 实参2 } ... };

(2)
接受引用的函数 ({.指派符1 = 实参1 , .指派符2 { 实参2 } ... }); (3)

T 的引用能以 T 类型的对象、T 类型的函数或可以隐式转换到 T 的对象初始化。引用一旦初始化,便无法引用另一对象。

引用在下列情形初始化:

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

解释

T - 被引用类型
引用 - 要初始化的引用变量
目标 - 使用的初始化器表达式
接受引用的函数 - 参数是引用类型(T &T && (C++11 起))的函数
返回引用的函数 - 返回类型是引用类型(T &T && (C++11 起))的函数
- 类名
引用成员 -  的引用类型(T &T && (C++11 起))非静态数据成员
指派符1, 指派符2, ... - 指派符
实参1, 实参2, ... - 初始化器列表中的初始化器

定义

对于两个类型 T1T2

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

初始化规则

如果引用初始化使用一般或指派 (C++20 起)列表初始化,那么遵循列表初始化的规则。

(C++11 起)

对于非列表初始化,给定目标 的类型为 U,引用要么直接绑定 到目标 ,要么绑定到从目标 转换到类型 T 的某个值。先考虑直接绑定,再考虑间接绑定,如果没有绑定可用,那么程序非良构。

在所有情况下,当两个类型的引用兼容关系被用于建立引用绑定的有效性时,如果标准转换序列非良构,那么需要建立该绑定的程序非良构。

直接绑定

如果满足以下所有条件:

  • 要初始化的引用是左值引用。
  • 目标 是非位域左值。
  • T 引用兼容 U

那么引用会绑定到目标 或它合适的基类子对象:

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
  • 目标 可以转换成某个类型 V 的左值,并且 T 引用兼容 V

那么引用会绑定到转换结果左值或它合适的基类子对象:

struct A {};
struct B : A { operator int&(); };
 
int& ir = B(); // ir 指代 B::operator int& 的结果
  • 否则,如果要初始化的引用是左值引用,并且 (C++11 起) T 没有 const 限定,或者具有 volatile 限定,那么程序非良构:
double& rd2 = 2.0; // 错误:不是左值,并且引用不是 const 的
int i = 2;
double& rd3 = i;   // 错误:类型不匹配,并且引用不是 const 的

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

  • 目标 是以下类别之一的值:
  • 右值
(C++11 前)
  • 非位域亡值
  • 类纯右值
  • 数组纯右值
  • 函数左值
(C++11 起)
(C++17 前)
  • 非位域右值
  • 函数左值
(C++17 起)
  • T 引用兼容 U

那么引用会绑定到目标 或它合适的基类子对象:

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

如果目标 是纯右值,那么会对它应用临时量实质化,此时会将该纯右值的类型视为调整后类型 P

  • P 是通过向目标 的类型(即 U)添加 T 的 cv 限定调整得到的。

这种情况下引用会绑定到结果对象或它合适的基类子对象。

(C++17 起)

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

  • U 是类类型。
  • T 不引用关联与 U
  • 目标 可以转换成某个类型 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 限定调整得到的。

这种情况下引用会绑定到结果对象或它合适的基类子对象。

(C++17 起)

间接绑定

如果直接绑定不可用,那么就会考虑间接绑定。此时 T 不能引用关联于 U

如果 TU 是类类型,会以通过用户定义转换进行的对类型 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 起)
  • 有括号表达式 (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:延续生存期
A a2(7); // 良构,但有悬垂引用
(C++20 起)

总而言之,临时量的生存期不能以进一步“传递”来延续:从绑定了该临时量的引用或数据成员初始化的第二引用不影响临时量的生存期。

注解

只有在函数形参声明中,函数返回类型声明中,类成员声明中,以及带 extern 说明符时,引用不需要与初始化器一同出现。

在解决 CWG 问题 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; // OK
 
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 从类类型目标 到另一引用兼容类型的转换只能是隐式的 可以是显式的
CWG 1295 C++11 引用可以绑定到位域亡值 已禁止
CWG 1299 C++98 临时量的定义不明确 使之明确
CWG 1571 C++98 间接绑定中的用户定义转换不会考虑目标 的类型 会考虑
CWG 1604 C++98 间接绑定不会考虑用户定义转换 会考虑
CWG 2352 C++98 引用兼容性不会考虑限定性转换 会考虑
CWG 2481 C++17 间接绑定中不会向临时量实质化的结果类型添加 cv 限定 会添加
CWG 2657 C++17 直接绑定中不会向临时量实质化的结果类型添加 cv 限定 会添加
CWG 2801 C++98 间接绑定允许有引用关联的类型 已禁止

参阅