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,这意味着它没有用户提供的构造函数(即只有默认、复制、移动 ctor)。
std::array<int, 3> arr0({1, 2, 3});
std::array<int, 3> arr1({{1, 2, 3}});
(..) 的初始化是直接初始化。这需要构造函数调用。在arr0 和arr1 的情况下,只有复制/移动构造函数是可行的。因此,这两个示例意味着从花括号初始化列表中创建一个临时的std::array,并将其复制/移动到目标。通过复制/移动省略,编译器被允许省略该复制/移动操作,即使它有副作用。
注意即使临时对象是纯右值,它也可能调用一个副本(语义上,在复制省略之前),因为 std::array 的移动 ctor 可能不会被隐式声明,例如如果它被删除了。
std::array<int, 3> arr6 = std::array<int, 3>({1, 2, 3});
std::array<int, 3> arr7 = std::array<int, 3>({{1, 2, 3}});
这些是复制初始化的示例。创建了两个临时对象:
- 通过braced-init-list
{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 };。这是considered a defect,将在 C++1y 中修复,但是建议的解决方案不是标准的一部分(DRWP 状态,请参见链接页面的顶部),因此您不能指望您的编译器也为 @987654346 实现它@。
因此,严格来说,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 指出的in a comment,仅保证您可以使用以下语法 [array.overview]/2 初始化std::array:
array a = { initializer-list };
您可以由此推断,如果大括号省略以T x { a }; 的形式被允许,语法
array a { initializer-list };
格式正确,含义相同。但是,不能保证std::array 实际上包含一个原始数组作为其唯一的数据成员(另请参阅LWG 2310)。我认为一个例子可能是部分专业化std::array<T, 2>,其中有两个数据成员T m0 和T m1。因此,不能断定
array 一个 {{ initializer-list }};
格式正确。不幸的是,这会导致无法保证为T x { a }; 初始化std::array 临时不带括号省略的方法,这也意味着奇数示例(1、3、5、7)不需要工作。
所有这些初始化std::array 的方法最终都会导致聚合初始化。它被定义为聚合成员的复制初始化。但是,使用花括号初始化列表的复制初始化仍然可以直接初始化聚合成员。例如:
struct foo { foo(int); foo(foo const&)=delete; };
std::array<foo, 2> arr0 = {1, 2}; // error: deleted copy-ctor
std::array<foo, 2> arr1 = {{1}, {2}}; // error/ill-formed, cannot initialize a
// possible member array from {1}
// (and too many initializers)
std::array<foo, 2> arr2 = {{{1}, {2}}}; // not guaranteed to work
第一个尝试分别从初始化子句 1 和 2 初始化数组元素。此复制初始化等效于foo arr0_0 = 1;,后者又等效于非法的foo arr0_0 = foo(1);(已删除copy-ctor)。
第二个不包含表达式列表,而是初始化器列表,因此它不满足 [array.overview]/2 的要求。在实践中,std::array 包含一个原始数组数据成员,它将(仅)从第一个初始化子句 {1} 初始化,第二个子句 {2} 然后是非法的。
第三个与第二个有相反的问题:如果存在 一个数组数据成员,它就可以工作,但不能保证。