命名空间
变体
操作

列表初始化 (C++11 起)

来自 cppreference.cn
< cpp‎ | 语言
 
 
C++ 语言
 
 

使用花括号初始值设定项列表初始化对象。

目录

[编辑] 语法

[编辑] 直接列表初始化

T object { arg1, arg2, ... };

T object{.des1 = arg1 , .des2 { arg2 } ... };

(C++20 起)
(1)
T { arg1, arg2, ... }

T {.des1 = arg1 , .des2 { arg2 } ... }

(C++20 起)
(2)
new T { arg1, arg2, ... }

new T {.des1 = arg1 , .des2 { arg2 } ... }

(C++20 起)
(3)
Class { T member { arg1, arg2, ... }; };

Class { T member {.des1 = arg1 , .des2 { arg2 } ... }; };

(C++20 起)
(4)
Class::Class() : member { arg1, arg2, ... } {...

Class::Class() : member {.des1 = arg1 , .des2 { arg2 } ...} {...

(C++20 起)
(5)

[编辑] 复制列表初始化

T object = { arg1, arg2, ... };

T object = {.des1 = arg1 , .des2 { arg2 } ... };

(C++20 起)
(6)
function ({ arg1, arg2, ... })

function ({.des1 = arg1 , .des2 { arg2 } ... })

(C++20 起)
(7)
return { arg1, arg2, ... };

return {.des1 = arg1 , .des2 { arg2 } ... };

(C++20 起)
(8)
object [{ arg1, arg2, ... }]

object [{.des1 = arg1 , .des2 { arg2 } ... }]

(C++20 起)
(9)
object = { arg1, arg2, ... }

object = {.des1 = arg1 , .des2 { arg2 } ... }

(C++20 起)
(10)
U ({ arg1, arg2, ... })

U ({.des1 = arg1 , .des2 { arg2 } ... })

(C++20 起)
(11)
Class { T member = { arg1, arg2, ... }; };

Class { T member = {.des1 = arg1 , .des2 { arg2 } ... }; };

(C++20 起)
(12)

列表初始化在以下情况发生

  • 直接列表初始化(显式和非显式构造函数都考虑)
1) 用花括号初始值设定项列表初始化具名变量
2) 用花括号初始值设定项列表初始化无名临时对象
3)new 表达式初始化具有动态存储期的对象,其中初始值设定项是花括号初始值设定项列表
4) 在不使用等号的非静态数据成员初始值设定项
5) 在构造函数的成员初始值设定项列表中,如果使用花括号初始值设定项列表
  • 复制列表初始化(显式和非显式构造函数都考虑,但只能调用非显式构造函数)
6) 在等号后用花括号初始值设定项列表初始化具名变量
7) 在函数调用表达式中,使用花括号初始值设定项列表作为参数,并且列表初始化用于初始化函数参数
8)return 语句中,使用花括号初始值设定项列表作为返回表达式,并且列表初始化用于初始化返回对象
9) 在使用用户定义operator[]下标表达式中,其中列表初始化用于初始化重载运算符的参数
10)赋值表达式中,其中列表初始化用于初始化重载运算符的参数
11) 函数式转换表达式或其他构造函数调用,其中使用花括号初始值设定项列表代替构造函数参数。复制列表初始化初始化构造函数的参数(注意;此示例中的类型U不是正在列表初始化的类型;而是U的构造函数的参数)
12) 在使用等号的非静态数据成员初始值设定项

[编辑] 解释

类型为(可能是 cv 限定的)T 的对象的列表初始化的效果是:

  • 如果花括号初始值设定项列表包含指定初始值设定项列表并且T不是引用类型,则T必须是聚合类。指定初始值设定项列表中的指定符中排序的标识符必须形成T的直接非静态数据成员中排序的标识符的子序列。聚合初始化被执行。
(C++20 起)
  • 否则,如果T是聚合类且花括号初始值设定项列表,不包含指定初始值设定项列表,(C++20 起)有一个相同或派生类型(可能带 cv 限定符)的单个初始值设定项子句,则对象由该初始值设定项子句初始化(对于复制列表初始化通过复制初始化,对于直接列表初始化通过直接初始化)。
  • 否则,如果T是字符数组,并且花括号初始值设定项列表有一个单一的初始值设定项子句是一个适当类型的字符串字面量,则该数组照常从字符串字面量初始化
  • 否则,如果花括号初始值设定项列表为空且T是具有默认构造函数的类类型,则执行值初始化
  • 否则,如果T是类类型,则分两个阶段考虑T的构造函数:
  • 如果前一阶段未产生匹配,则T的所有构造函数都参与重载决议,以花括号初始值设定项列表的初始值设定项子句组成的参数集为基础,但限制只允许非窄化转换。如果此阶段为复制列表初始化产生了显式构造函数作为最佳匹配,则编译失败(注意,在简单的复制初始化中,根本不考虑显式构造函数)。
  • 否则,如果T是具有固定底层类型U枚举类型,花括号初始值设定项列表只有一个初始值设定项v,并且满足以下所有条件,则枚举用将v转换为U的结果初始化:
    • 初始化是直接列表初始化。
    • v标量类型
    • v 可以隐式转换为U
    • vU的转换是非窄化的。
(C++17 起)
  • 否则(如果T不是类类型),如果花括号初始值设定项列表只有一个初始值设定项子句,并且T不是引用类型,或者T是引用类型,其引用类型与初始值设定项子句的类型相同或为其基类,则T直接初始化(在直接列表初始化中)或复制初始化(在复制列表初始化中),但窄化转换不允许。
  • 否则,如果T是与初始值设定项子句的类型不兼容的引用类型
  • 类型为 T 引用的临时 prvalue 被复制列表初始化,并且引用绑定到该临时对象(如果引用是非 const 左值引用,则此操作失败)。
(C++17 前)
  • 生成一个 prvalue。该 prvalue 通过复制列表初始化初始化其结果对象。然后,该 prvalue 用于直接初始化引用(如果引用是非 const 左值引用,则此操作失败)。临时对象的类型是 T 引用的类型,除非 T 是“引用到未知边界的 U 类型数组”,在这种情况下,临时对象的类型是声明 U x[] Hx 的类型,其中 H 是初始值列表(C++20 起)
(C++17 起)
  • 否则,如果花括号初始值设定项列表没有初始值设定项子句,则T值初始化

[编辑] 列表初始化 std::initializer_list

类型为std::initializer_list<E>的对象由初始化列表构造,如同编译器生成实体化(C++17 起)了类型为“Nconst E的数组”的prvalue,其中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}});
}
 
// The initialization above will be implemented in a way roughly equivalent to below,
// assuming that the compiler can construct an initializer_list object with a pair of
// pointers, and with the understanding that `__b` does not outlive the call to `f`.
 
void g(float x)
{
    const double __a[3] = {double{1}, double{x}, double{3}}; // backing array
    f(std::initializer_list<double>(__a, __a + 3));
}
 
void h()
{
    static constexpr double __b[3] =
        {double{1}, double{2}, double{3}}; // backing array
    f(std::initializer_list<double>(__b, __b + 3));
}
 
void r()
{
    const A __c[3] = {A{1}, A{2}, A{3}}; // backing array
    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}); // the result is unspecified:
                                              // the back arrays can share
                                              // storage within {1, 2, 3, 4}

[编辑] 窄化转换

列表初始化通过禁止以下转换来限制允许的隐式转换

  • 从浮点类型到整数类型的转换
  • 从浮点类型 T 到另一个浮点类型的转换,如果其浮点转换等级既不大于也不等于 T 的等级,除非转换结果是常量表达式且满足以下条件之一:
    • 转换后的值是有限的,并且转换没有溢出。
    • 转换前后的值都是非有限的。
  • 从整数类型到浮点类型的转换,除非源是一个常量表达式,其值可以精确地存储在目标类型中
  • 从整数或无作用域枚举类型到整数类型的转换,如果目标类型不能表示原始类型的所有值,除非
    • 源是一个位域,其宽度w小于其类型(或对于枚举类型,其底层类型)的宽度,并且目标类型可以表示宽度为w且与原始类型具有相同符号性的假设扩展整数类型的所有值,或者
    • 源是一个常量表达式,其值可以精确地存储在目标类型中
  • 从指针类型或指向成员的指针类型到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 {}; // aggregate
 
struct Q     // non-aggregate
{
    Q() = default;
    Q(Q const&) = default;
    Q(std::initializer_list<Q>) {}
};
 
int main()
{
    X x;
    X x2 = X{x}; // copy-constructor (not aggregate initialization)
 
    Q q;
    Q q2 = Q{q}; // initializer-list constructor (not copy constructor)
}

一些编译器(例如 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}; // list-initialization of a non-static member
    std::vector<int> mem2;
 
    Foo() : mem2{-1, -2, -3} {} // list-initialization of a member in constructor
};
 
std::pair<std::string, std::string> f(std::pair<std::string, std::string> p)
{
    return {p.second, p.first}; // list-initialization in return statement
}
 
int main()
{
    int n0{};  // value-initialization (to zero)
    int n1{1}; // direct-list-initialization
 
    std::string s1{'a', 'b', 'c', 'd'}; // initializer-list constructor call
    std::string s2{s1, 2, 2};           // regular constructor call
    std::string s3{0x61, 'a'}; // initializer-list ctor is preferred to (int, char)
 
    int n2 = {1}; // copy-list-initialization
    double d = double{1.2}; // list-initialization of a prvalue, then copy-init
    auto s4 = std::string{"HelloWorld"}; // same as above, no temporary
                                         // created since C++17
 
    std::map<int, std::string> m = // nested list-initialization
    {
        {1, "a"},
        {2, {'a', 'b', 'c'}},
        {3, s1}
    };
 
    std::cout << f({"hello", "world"}).first // list-initialization in function call
              << '\n';
 
    const int (&ar)[2] = {1, 2}; // binds an lvalue reference to a temporary array
    int&& r1 = {1}; // binds an rvalue reference to a temporary int
//  int& r2 = {2}; // error: cannot bind rvalue to a non-const lvalue ref
 
//  int bad{1.0}; // error: narrowing conversion
    unsigned char uc1{10}; // okay
//  unsigned char uc2{-1}; // error: narrowing conversion
 
    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); // has effect of [[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时,初始化列表构造函数输给复制构造函数
当列表初始化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限定符 忽略
CWG 2864 C++11 溢出的浮点转换不是窄化转换 它们是窄化转换
P1957R2 C++11 从指针/指向成员的指针到bool的转换不是窄化转换
bool不是窄化转换
被认为是窄化转换
P2752R3 C++11 生命周期重叠的后备数组不能重叠 它们可能重叠

[编辑] 另见