[C++]现代C++语言核心特性解析 Chapter 9
列表初始化(C++11 C++20)
1 回顾变量初始化
一般来说,我们称使用括号初始化的方式叫作直接初始化,而使用等号初始化的方式叫作拷贝初始化(复制初始化)。请注意,这里使用等号对变量初始化并不是调用等号运算符的赋值操作。实际情况是,等号是拷贝初始化,调用的依然是直接初始化对应的构造函数,只不过这里是隐式调用而已。
2 使用列表初始化 C++11
C++11标准引入了列表初始化,它使用大括号{}对变量进行初始化,和传统变量初始化的规则一样,它也区分为直接初始化和拷贝初始化。它支持隐式调用多参数的构造函数。如果不希望进行隐式转换,需要在特定的构造函数上声明explicit。
1 |
|
3 std::initializer_list详解
标准容器之所以能够支持列表初始化,离不开编译器支持的同时,它们自己也必须满足一个条件:支持std::initializer_list为形参的构造函数。
std::initializer_list简单地说就是一个支持begin、end以及size成员函数的类模板。编译器负责将列表里的元素(大括号包含的内容)构造为一个std::initializer_list的对象,然后寻找标准容器中支持std:: initializer_list为形参的构造函数并调用它。标准容器的构造函数只需要调用std::initializer_list对象的begin和end函数,在循环中对本对象进行初始化。
std:: initializer_list的begin和end函数并不是返回的迭代器对象,而是一个常量对象指针const T *。
4 使用列表初始化的注意事项
4.1 隐式缩窄转换问题
1 |
|
这段代码中变量y的初始化明显是一个隐式缩窄转换,这在传统变量初始化中是没有问题的,代码能顺利通过编译。但是如果采用列表初始化,比如char z{ x },根据标准编译器通常会给出一个错误,MSVC和CLang就是这么做的,而GCC有些不同,它只是给出了警告。
C++中的隐式缩窄转换:
- 从浮点类型转换整数类型。
- 从long double转换到double或float,或从double转换到float,除非转换源是常量表达式以及转换后的实际值在目标可以表示的值范围内。
- 从整数类型或非强枚举类型转换到浮点类型,除非转换源是常量表达式,转换后的实际值适合目标类型并且能够将生成目标类型的目标值转换回原始类型的原始值。
- 从整数类型或非强枚举类型转换到不能代表所有原始类型值的整数类型,除非源是一个常量表达式,其值在转换之后能够适合目标类型。
4.2 列表初始化的优先级问题
如果有一个类同时拥有满足列表初始化的构造函数,且其中一个是以std::initializer_list为参数,那么编译器将优先以std::initializer_ list为参数构造函数。
5 指定初始化 C++20
为了提高数据成员初始化的可读性和灵活性,C++20标准中引入了指定初始化的特性。该特性允许指定初始化数据成员的名称,从而使代码意图更加明确。
1 |
|
注意事项
- 它要求对象必须是一个聚合类型
- 指定的数据成员必须是非静态数据成员
- 每个非静态数据成员最多只能初始化一次
- 非静态数据成员的初始化必须按照声明的顺序进行
- 针对联合体中的数据成员只能初始化一次,不能同时指定
- 不能嵌套指定初始化数据成员
- 在C++20中,一旦使用指定初始化,就不能混用其他方法对数据成员初始化了