列表初始化被非正式地称为“统一初始化”,因为它的含义和行为是相同的不管你如何调用它。
当然,C++ 就是 C++,“预期”的事情并不总是发生。
直接列表初始化和复制列表初始化的行为之间基本上存在三个主要区别。第一个是您最常遇到的:如果列表初始化将调用标记的构造函数explicit
,那么如果列表初始化形式是复制列表初始化,则会出现编译错误。
这种行为差异定义为[超过匹配列表]/1 https://timsong-cpp.github.io/cppwp/n4659/over.match.list:
在复制列表初始化中,如果explicit
选择了构造函数,初始化格式错误。
这是重载解析的函数。
第二个主要区别(C++17 的新功能)是,给定具有固定基础大小的枚举类型,您可以使用基础类型的值对其执行直接列表初始化。但是你cannot从这样的值执行复制列表初始化。所以enumeration e{value};
有效,但没有enumeration e = {value};
.
第三个主要区别(对于 C++17 来说也是新的)与花括号初始化列表的行为有关auto
扣除。通常情况下,auto
其行为与模板参数推导并不太明显。但与模板参数推导不同的是,auto
可以从花括号初始化列表初始化。
如果你初始化一个auto
使用直接列表初始化与列表中的单个表达式的变量,编译器将推断该变量是表达式的类型:
auto x{50.0f}; //x is a `float`.
听起来很有道理。但如果对复制列表初始化做完全相同的事情,它会always可以推导出initializer_list<T>
, where T
是初始化器的类型:
auto x = {50.0f}; //x is an `initializer_list<float>`
非常非常统一。 ;)
值得庆幸的是,如果您在花括号初始化列表中使用多个初始化程序,则直接列表初始化auto
-推导的变量总是会给出编译错误,而复制列表初始化只会给出更长的时间initializer_list
。因此自动推导的直接列表初始化永远不会给出initializer_list
,而自动推导出的复制列表初始化总是会的。
有一些微小的差异很少影响初始化的预期行为。在这些情况下,来自单个值的列表初始化将根据列表初始化形式使用复制或直接(非列表)初始化。这些案例是:
-
从与正在初始化的聚合类型相同的单个值初始化聚合。这绕过了聚合初始化。
-
从单个值初始化非类、非枚举类型。
-
初始化参考。
这些不仅不会经常发生,而且基本上不会真正改变代码的含义。非类类型没有显式构造函数,因此复制和直接初始化之间的区别主要是学术性的。参考文献也是如此。总体情况实际上只是从给定值执行复制/移动。