以下是8种在C ++ 11中声明和初始化数组的方法,在g++
下似乎没问题:
/*0*/ std::array<int, 3> arr0({1, 2, 3});
/*1*/ std::array<int, 3> arr1({{1, 2, 3}});
/*2*/ std::array<int, 3> arr2{1, 2, 3};
/*3*/ std::array<int, 3> arr3{{1, 2, 3}};
/*4*/ std::array<int, 3> arr4 = {1, 2, 3};
/*5*/ std::array<int, 3> arr5 = {{1, 2, 3}};
/*6*/ std::array<int, 3> arr6 = std::array<int, 3>({1, 2, 3});
/*7*/ std::array<int, 3> arr7 = std::array<int, 3>({{1, 2, 3}});
根据严格标准(以及即将推出的C ++ 14标准),正确的是什么? 什么是最常见/最常用的和那些要避免的(以及为什么原因)?
答案 0 :(得分:19)
C ++ 11摘要/ TL; DR
std::array
是否包含原始数组。因此,不需要实施例1,3,5,7。但是,我不知道标准库实现它们不起作用(实际上)。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
的情况下,只有复制/移动构造函数是可行的。因此,这两个示例意味着从braced-init-list创建临时std::array
,并将其复制/移动到目标。通过复制/移动省略,编译器允许忽略复制/移动操作,即使它有副作用。
<子> N.B。即使临时值是prvalues,它也可以调用副本(在语义上,在复制省略之前),因为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}});
这些是复制初始化的示例。有两个临时创建:
{1, 2, 3}
来调用复制/移动构造函数std::array<int, 3>(..)
后者临时将被复制/移动到指定的目标变量。两个临时工的创造都可以省略。
据我所知,一个实现可以写一个([container.requirements.general]排除了这种可能性,对David Krauss表示赞赏,见this discussion。)explicit array(array const&) = default;
构造函数而不违反标准;这会使这些例子形成不良。
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}};
这是聚合初始化。他们都直接&#34;初始化std::array
,而不调用std::array
的构造函数,也没有(语义上)创建临时数组。 std::array
的成员通过复制初始化进行初始化(见下文)。
关于大括号的主题:
在C ++ 11标准中,大括号仅适用于T x = { a };
形式的声明,但不适用于T x { a };
。这是considered a defect并将在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})
使用复制初始化:参数传递的初始化也是copy-init。然而,&#34;在T x = { a };
&#34; 形式的声明中,也不允许括号省略以进行论证传递,因为它不是声明,当然不是那种形式。
关于聚合初始化的主题:
由于Johannes Schaub指出in a comment,仅保证您可以使用以下语法[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 };
临时w / o括号内容的方法,也意味着奇数例子(1,3,5, 7)不需要工作。
所有这些初始化std::array
的方法最终都会导致聚合初始化。它被定义为聚合成员的复制初始化。但是,使用braced-init-list进行复制初始化仍然可以直接初始化聚合成员。例如:
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);
相当于std::array
,这是非法的(已删除的复制文件)。
第二个不包含表达式列表,而是一个初始化器列表,因此它不满足[array.overview] / 2的要求。实际上,{1}
包含一个原始数组数据成员,它将从第一个初始化子句{2}
初始化(仅),第二个子句{{1}}则是非法的。
第三个问题与第二个问题相反:如果是一个数组数据成员,它就可以工作,但是不能保证。
答案 1 :(得分:3)
我认为他们都严格遵守,除了可能arr2
。我会采用arr3
方式,因为它简洁明了,绝对有效。如果arr2
有效(我只是不确定),那实际上会更好。
组合parens和大括号(0和1)从来不适合我,等于(4和5)是好的,但我只是喜欢较短的版本,而6和7只是荒谬的冗长。
但是,在Herb Sutter's "almost always auto" style之后,您可能想要采用另一种方式:
auto arr8 = std::array<int, 3>{{1, 2, 3}};
答案 2 :(得分:1)
此answer链接bug report,在使用-Wmissing-braces
时默认情况下不再启用-Wall
。如果您启用-Wmissing-braces
,则gcc
会抱怨0,2,4和6(与clang
相同)。
对于T a = { ... }
但不是T a { }
形式的陈述,允许进行括号省略。
Why is the C++ initializer_list behavior for std::vector and std::array different?
以下是James McNellis的回答:
但是,这些额外的括号可能只能在“宣言中”中删除 形式T x = {a};“(C ++11§8.5.1/ 11),即旧式 =使用。允许括号省略的此规则不适用于直接列表初始化。这里的一个脚注写着:“大括号不能被省略 在列表初始化的其他用途中。“
有关此限制的缺陷报告:CWG defect #1270。如果采用提议的解决方案,则允许使用大括号来进行其他形式的列表初始化,...
如果采纳提议的决议,将允许支持 对于其他形式的列表初始化,以下将是 以及形成: std :: array y {1,2,3,4};
并且Xeo回答:
...而std :: array没有构造函数,{1,2,3,4}支撑 init-list实际上并不是解释为std :: initializer_list,而是 std :: array的内部C样式数组的聚合初始化 (这是第二组括号的来源:一个用于std :: array, 一个用于内部C风格的成员数组。)
std::array
没有带initializer_list
的构造函数。因此,它被视为聚合初始化。如果是这样,它将看起来像这样:
#include <array>
#include <initializer_list>
struct test {
int inner[3];
test(std::initializer_list<int> list) {
std::copy(list.begin(), list.end(), inner);
}
};
#include <iostream>
int main() {
test t{1, 2, 3};
test t2({1, 2, 3});
test t3 = {1, 2, 3};
test t4 = test({1, 2, 3});
for (int i = 0; i < 3; i++)
std::cout << t.inner[i];
for (int i = 0; i < 3; i++)
std::cout << t2.inner[i];
for (int i = 0; i < 3; i++)
std::cout << t3.inner[i];
for (int i = 0; i < 3; i++)
std::cout << t4.inner[i];
}
答案 3 :(得分:0)
最后2个是多余的:您可以在赋值的右侧使用6种形式的数组声明。此外,如果您的编译器没有优化副本,则这些版本的效率会降低。
初始化列表构造函数需要双括号,因此第三行无效。