List-initialization (since C++11)
从 花括号包围的初始化列表 初始化对象。
目录 |
语法
直接列表初始化
T 对象
{
参数1, 参数2, ...
};
|
(1) | ||||||||
T
{
参数1, 参数2, ...
}
|
(2) | ||||||||
new
T
{
参数1, 参数2, ...
}
|
(3) | ||||||||
类
{
T 成员
{
参数1, 参数2, ...
}; };
|
(4) | ||||||||
类
::
类
() :
成员
{
参数1, 参数2, ...
} {...
|
(5) | ||||||||
复制列表初始化
T 对象
= {
参数1, 参数2, ...
};
|
(6) | ||||||||
函数
({
参数1, 参数2, ...
})
|
(7) | ||||||||
return {
参数1, 参数2, ...
};
|
(8) | ||||||||
对象
[{
参数1, 参数2, ...
}]
|
(9) | ||||||||
对象
= {
参数1, 参数2, ...
}
|
(10) | ||||||||
U
({
参数1, 参数2, ...
})
|
(11) | ||||||||
类
{
T 成员
= {
参数1, 参数2, ...
}; };
|
(12) | ||||||||
列表初始化在以下情况下执行:
- 直接列表初始化(同时考虑显式和隐式构造函数)
- 复制列表初始化(同时考虑显式和隐式构造函数,但只能调用非显式构造函数)
U
并非正在进行列表初始化的类型;实际初始化的是
U
的构造函数的参数)
说明
(可能带有 cv 限定符的)
T
类型对象的列表初始化效果如下:
| (C++20 起) |
-
若
T为聚合类且花括号包围的初始化列表 (不包含指派初始化列表) (C++20 起) 仅含单个与当前类型相同或派生类型(可能带有 cv 限定)的初始化子句,则通过该子句初始化对象(对于复制列表初始化执行 复制初始化 ,对于直接列表初始化执行 直接初始化 )。 -
否则,若
T为字符数组且花括号包围的初始化列表仅含单个类型匹配的字符串字面量初始化子句,则按 常规方式从字符串字面量初始化数组 。
-
否则,如果花括号包围的初始化列表为空且
T是具有默认构造函数的类类型,则执行 值初始化 。
-
否则,若
T是 std::initializer_list 的特化,则对象将按照 下文 所述方式进行初始化。
-
否则,如果
T是类类型,则分两个阶段考虑T的构造函数:
-
- 所有以 std::initializer_list 作为唯一参数,或作为第一个参数(其余参数具有默认值)的构造函数,都会通过 重载决议 与类型为 std::initializer_list 的单个参数进行匹配检查。
-
-
若前一阶段未产生匹配,则
T的所有构造函数都会参与针对由花括号初始化列表的初始化子句所组成的参数集的 重载决议 ,且仅允许非窄化转换。若此阶段在复制列表初始化中产生显式构造函数作为最佳匹配,则编译失败(注意:在简单复制初始化中,完全不会考虑显式构造函数)。
-
若前一阶段未产生匹配,则
| (C++17 起) |
-
否则(如果
T不是类类型),且花括号初始化列表仅包含一个初始化子句,同时T不是引用类型,或者是引用类型且其引用类型与初始化子句类型相同或是其基类,则对T执行 直接初始化 (在直接列表初始化中)或 拷贝初始化 (在拷贝列表初始化中),但不允许窄化转换。
-
否则,如果
T是一个与初始化子句类型不兼容的引用类型:
|
(C++17 前) |
|
(C++17 起) |
-
否则,如果花括号包围的初始化列表没有初始化子句,则
T将进行 值初始化 。
列表初始化 std::initializer_list
类型为 std:: initializer_list < E > 的对象从初始化列表构造时,如同编译器生成 并 物化 (C++17 起) 了一个类型为“ N 个 const E 的数组”的 纯右值 ,其中 N 是初始化列表中初始化子句的数量;这被称为初始化列表的 后备数组 。
后备数组的每个元素都会使用初始化列表中对应初始化子句进行 拷贝初始化 ,而 std:: initializer_list < E > 对象则被构造为引用该数组。为拷贝选择的构造函数或转换函数必须在初始化列表的上下文中是 可访问的 。若初始化任何元素需要进行窄化转换,则程序非良构。
后备数组与其他任何 临时对象 具有相同的生命周期,但从后备数组初始化 std::initializer_list 对象时,会延长该数组的生命周期,其机制与 将引用绑定到临时对象 完全一致。
void f(std::initializer_list<double> il); void g(float x) { f({1, x, 3}); } void h() { f({1, 2, 3}); } struct A { mutable int i; }; void q(std::initializer_list<A>); void r() { q({A{1}, A{2}, A{3}}); } // 上述初始化将以大致等价于以下方式实现, // 假设编译器能够通过一对指针构造initializer_list对象, // 并理解`__b`不会在`f`函数调用结束后继续存在。 void g(float x) { const double __a[3] = {double{1}, double{x}, double{3}}; // 后备数组 f(std::initializer_list<double>(__a, __a + 3)); } void h() { static constexpr double __b[3] = {double{1}, double{2}, double{3}}; // 后备数组 f(std::initializer_list<double>(__b, __b + 3)); } void r() { const A __c[3] = {A{1}, A{2}, A{3}}; // 后备数组 q(std::initializer_list<A>(__c, __c + 3)); }
所有后备数组是否互不相同(即存储于 非重叠对象 中)是未指定的:
bool fun(std::initializer_list<int> il1, std::initializer_list<int> il2) { return il2.begin() == il1.begin() + 1; } bool overlapping = fun({1, 2, 3}, {2, 3, 4}); // 结果未指定: // 底层数组可能共享 // {1, 2, 3, 4} 的存储空间
窄化转换
列表初始化通过禁止以下内容来限制允许的 隐式转换 :
- 从浮点类型到整数类型的转换
-
从浮点类型
T到另一个浮点类型的转换,且目标类型的 浮点转换等级 既不高于也不等于T的等级,除非转换结果是 常量表达式 且满足以下任一条件:- 转换后的值是有限的,且转换不会溢出。
- 转换前后的值均为非有限值。
- 从整数类型到浮点类型的转换,除非源是一个常量表达式且其值可以精确存储在目标类型中
- 从整数类型或无作用域枚举类型转换为无法表示原始类型所有值的整数类型,除非:
- 从指针类型或指向成员类型的指针到 bool 的转换
注释
每个初始化子句都 先序于 其后出现在花括号初始化列表中的任何初始化子句。这与 函数调用表达式 的参数形成对比,后者是 无顺序的 (C++17 前) 不确定顺序的 (C++17 起) 。
花括号包围的初始化列表不是表达式,因此没有类型,例如 decltype ( { 1 , 2 } ) 是病式的。没有类型意味着模板类型推导无法推导出与花括号初始化列表匹配的类型,因此对于声明 template < class T > void f ( T ) ; ,表达式 f ( { 1 , 2 , 3 } ) 是病式的。然而,模板参数可以通过其他方式推导,如 std:: vector < int > v ( std:: istream_iterator < int > ( std:: cin ) , { } ) 的情况,其中迭代器类型通过第一个参数推导,但也用于第二个参数位置。对于 使用关键字 auto 的类型推导 存在特殊例外,在复制列表初始化中会将任何花括号初始化列表推导为 std::initializer_list 。
同样因为花括号包围的初始化列表没有类型,当它被用作重载函数调用的参数时, 重载解析的特殊规则 将会适用。
聚合体直接从相同类型的单个初始化子句的花括号初始化列表进行复制/移动初始化,但非聚合体优先考虑 std::initializer_list 构造函数:
struct X {}; // 聚合类型 struct Q // 非聚合类型 { Q() = default; Q(Q const&) = default; Q(std::initializer_list<Q>) {} }; int main() { X x; X x2 = X{x}; // 拷贝构造函数(非聚合初始化) Q q; Q q2 = Q{q}; // 初始化列表构造函数(非拷贝构造函数) }
某些编译器(例如 gcc 10)在 C++20 模式下仅将指针或成员指针向 bool 的转换视为窄化转换。
| 功能测试宏 | 值 | 标准 | 功能特性 |
|---|---|---|---|
__cpp_initializer_lists
|
200806L
|
(C++11) | 列表初始化与 std::initializer_list |
示例
#include <iostream> #include <map> #include <string> #include <vector> struct Foo { std::vector<int> mem = {1, 2, 3}; // 非静态成员的列表初始化 std::vector<int> mem2; Foo() : mem2{-1, -2, -3} {} // 构造函数中成员的列表初始化 }; std::pair<std::string, std::string> f(std::pair<std::string, std::string> p) { return {p.second, p.first}; // return语句中的列表初始化 } int main() { int n0{}; // 值初始化(置零) int n1{1}; // 直接列表初始化 std::string s1{'a', 'b', 'c', 'd'}; // 初始化列表构造函数调用 std::string s2{s1, 2, 2}; // 常规构造函数调用 std::string s3{0x61, 'a'}; // 初始化列表构造函数优先于(int, char)构造函数 int n2 = {1}; // 拷贝列表初始化 double d = double{1.2}; // 纯右值的列表初始化,然后拷贝初始化 auto s4 = std::string{"HelloWorld"}; // 同上,自C++17起不创建临时对象 // std::map<int, std::string> m = // 嵌套列表初始化 { {1, "a"}, {2, {'a', 'b', 'c'}}, {3, s1} }; std::cout << f({"hello", "world"}).first // 函数调用中的列表初始化 << '\n'; const int (&ar)[2] = {1, 2}; // 将左值引用绑定到临时数组 int&& r1 = {1}; // 将右值引用绑定到临时int // int& r2 = {2}; // 错误:无法将右值绑定到非const左值引用 // int bad{1.0}; // 错误:窄化转换 unsigned char uc1{10}; // 正确 // unsigned char uc2{-1}; // 错误:窄化转换 Foo f; std::cout << n0 << ' ' << n1 << ' ' << n2 << '\n' << s1 << ' ' << s2 << ' ' << s3 << '\n'; for (auto p : m) std::cout << p.first << ' ' << p.second << '\n'; for (auto n : f.mem) std::cout << n << ' '; for (auto n : f.mem2) std::cout << n << ' '; std::cout << '\n'; [](...){}(d, ar, r1, uc1); // 具有[[maybe_unused]]的效果 }
输出:
world 0 1 1 abcd cd aa 1 a 2 abc 3 abcd 1 2 3 -1 -2 -3
缺陷报告
以下行为变更缺陷报告被追溯应用于先前发布的C++标准。
| 缺陷报告 | 应用于 | 发布时的行为 | 正确行为 |
|---|---|---|---|
| CWG 1288 | C++11 | 使用单初始化子句的花括号初始化列表对引用进行列表初始化时,总是将引用绑定到临时对象 | 若有效则绑定到该初始化子句 |
| CWG 1290 | C++11 | 后备数组的生命周期未正确定义 | 与其他临时对象规范保持一致 |
| CWG 1324 | C++11 |
从
{}
初始化时优先考虑普通初始化
|
优先考虑聚合初始化 |
| CWG 1418 | C++11 | 后备数组类型缺少 const 限定符 | 添加 const 限定符 |
| CWG 1467 | C++11 |
禁止聚合体和字符数组的同类型初始化;
单子句列表时初始化列表构造函数优先于拷贝构造函数 |
允许同类型初始化;
单子句列表直接初始化 |
| CWG 1494 | C++11 |
使用不兼容类型的初始化子句列表初始化引用时,
未明确创建的临时对象是直接列表初始化还是拷贝列表初始化 |
取决于引用的初始化方式 |
| CWG 2137 | C++11 |
从
{X}
列表初始化
X
时,初始化列表构造函数不敌拷贝构造函数
|
非聚合类型优先考虑初始化列表 |
| CWG 2252 | C++17 | 枚举类型可从非标量值进行列表初始化 | 禁止该行为 |
| CWG 2267 | C++11 |
CWG 1494
的解决方案明确
临时对象可被直接列表初始化 |
列表初始化引用时采用拷贝列表初始化 |
| CWG 2374 | C++17 | 枚举的直接列表初始化允许过多源类型 | 限制源类型范围 |
| CWG 2627 | C++11 |
较大整数类型的窄位域可提升为较小整数类型,
但仍被视为窄化转换 |
不视为窄化转换 |
| CWG 2713 | C++20 | 聚合类的引用无法通过指定初始化列表初始化 | 允许该操作 |
| CWG 2830 | C++11 | 列表初始化未忽略顶层cv限定符 | 忽略顶层cv限定符 |
| CWG 2864 | C++11 | 溢出的浮点转换不被视为窄化转换 | 视为窄化转换 |
| P1957R2 | C++11 | 指针/成员指针到 bool 的转换不被视为窄化转换 | 视为窄化转换 |
| P2752R3 | C++11 | 生命周期重叠的后备数组不能重叠 | 允许重叠 |