C++11摘要 / TL;DR
- 由于花括号省略缺陷,示例0、2、6不需要工作。然而,最近版本的编译器实现了该缺陷的建议解决方案,因此这些示例将会工作。
- 由于未指定
std::array
是否包含原始数组,因此不需要使示例1、3、5、7工作。但是,我不知道有哪个标准库实现它们不工作(在实践中)。
- 示例4将始终工作:
std::array<int, 3> arr4 = {1, 2, 3};
我更喜欢第4版或第2版(带有花括号省略修复),因为它们直接初始化并且需要/可能工作。
对于Sutter的AAA样式,您可以使用auto arrAAA = std::array<int, 3>{1, 2, 3};
,但这需要花括号省略修复。
std::array
要求是一个聚合体 [array.overview]/2,这意味着它没有用户提供的构造函数(即只有默认、拷贝和移动构造函数)。
std::array<int, 3> arr0({1, 2, 3});
std::array<int, 3> arr1({{1, 2, 3}});
使用
(..)
进行初始化是直接初始化,这需要调用构造函数。在
arr0
和
arr1
的情况下,只有复制/移动构造函数是可行的。因此,这两个例子意味着
从大括号初始化列表创建一个临时的 std::array
,并将其复制/移动到目标位置。通过复制/移动省略,即使它具有副作用,编译器也
允许省略该复制/移动操作。
请注意,即使临时值是 prvalues,由于 std::array
的移动构造函数可能没有被隐式声明(例如如果它被删除了),因此可能会触发一次复制(在复制省略之前语义上)。
std::array<int, 3> arr6 = std::array<int, 3>({1, 2, 3});
std::array<int, 3> arr7 = std::array<int, 3>({{1, 2, 3}});
以下是复制初始化的示例。创建了两个临时对象:
- 通过花括号初始化列表
{1, 2, 3}
调用复制/移动构造函数
- 通过表达式
std::array<int, 3>(..)
然后将后一个临时对象复制/移动到命名的目标变量。可以省略创建这两个临时对象。
据我所知,实现可能编写一个
explicit array(array const&) = default;
构造函数并且不违反标准;这将使得上述示例无效。(通过 [container.requirements.general] 规则出现了这种可能性,感谢 David Krauss,参见
this discussion。)
std::array<int, 3> arr2{1, 2, 3};
std::array<int, 3> arr3{{1, 2, 3}};
std::array<int, 3> arr4 = {1, 2, 3};
std::array<int, 3> arr5 = {{1, 2, 3}};
这是聚合初始化。它们都“直接”初始化
std::array
,而不调用
std::array
的构造函数,也不(语义上)创建临时数组。
std::array
的成员通过复制初始化进行初始化(见下文)。
关于花括号省略的话题:
在C++11标准中,花括号省略只适用于形如T x = { a };
的声明,但不适用于T x { a };
。这被认为是一个缺陷,并将在C++1y中得到修复,但是提议的解决方案不是标准的一部分(DRWP状态,请参见链接页面顶部),因此您不能指望编译器也为T x { a };
实现它。
因此,std::array<int, 3> arr2{1, 2, 3};
(示例0、2、6)严格来说是不合法的。据我所知,最近版本的clang++和g++已经允许在T x { a };
中使用花括号省略。
在示例6中,
std :: array<int,3>({1,2,3})
使用复制初始化:参数传递的初始化也是复制初始化。然而,括号省略的缺陷限制
“在形式为T x = {a};
的声明中”也禁止了参数传递的括号省略,因为它不是一个声明,当然也不是那种形式的声明。
关于聚合初始化:
正如 Johannes Schaub 在评论中指出的那样,只有以下语法[array.overview]/2可以保证您可以使用以下语法初始化std::array
:
array<T, N> a = { initializer-list };
如果在形式T x { a };
中允许花括号省略,您可以从中推断出以下语法:
array<T, N> a { initializer-list };
这段代码是格式良好的,并且具有相同的含义。然而,并不能保证std::array
实际上包含一个原始数组作为其唯一的数据成员(也可以参见LWG 2310)。我认为一个例子可以是部分特化std::array<T, 2>
,其中有两个数据成员T m0
和T m1
。因此,不能得出结论
array<T, N> a {{ initializer-list }};
是格式良好的。不幸的是,这导致了这样一种情况,即没有保证的方法初始化一个std::array
临时对象,而没有使用花括号省略T x { a };
,这也意味着奇怪的例子(1、3、5、7)不需要工作。
所有这些初始化
std::array
的方式最终都会导致聚合初始化。它被定义为聚合成员的复制初始化。然而,使用花括号初始化列表的复制初始化仍然可以直接初始化聚合成员。例如:
struct foo { foo(int); foo(foo const&)=delete; };
std::array<foo, 2> arr0 = {1, 2};
std::array<foo, 2> arr1 = {{1}, {2}};
std::array<foo, 2> arr2 = {{{1}, {2}}};
第一个尝试从初始化程序列表中分别初始化数组元素
1
和
2
。这个复制初始化等同于
foo arr0_0 = 1;
,进而等同于
foo arr0_0 = foo(1);
,但这是不合法的(已删除复制构造函数)。
第二个不包含表达式列表,而是包含初始化程序列表,因此它不满足[array.overview]/2的要求。在实践中,
std::array
包含一个原始数组数据成员,该成员将仅从第一个初始化程序列表
{1}
初始化,然后第二个程序列表
{2}
是非法的。
第三个与第二个问题相反:如果有一个数组数据成员,则可以正常工作,但不能保证有这样的成员。
arr0
和arr1
能够正常工作;它们是否调用了移动构造函数?如果是这样,它们的含义就与 2-5 不同(类似于 6 和 7)。 - dyparr0
,因此您的编号增加了混淆(至少对我来说是这样)。 - M.M