C++ 11中的数组声明和初始化

Vin*_*ent 11 c++ initialization c++11 stdarray c++14

以下是在C++ 11中声明和初始化数组的8种方法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}});
Run Code Online (Sandbox Code Playgroud)

根据严格标准(以及即将推出的C++ 14标准),正确的是什么?什么是最常见的/使用的和那些要避免的(以及为什么)?

dyp*_*dyp 19

C++ 11 summary/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};,但这需要支撑elision修复.


std::array 需要是一个聚合[array.overview]/2,这意味着它没有用户提供的构造函数(即只有默认,复制,移动ctor).


std::array<int, 3> arr0({1, 2, 3});
std::array<int, 3> arr1({{1, 2, 3}});
Run Code Online (Sandbox Code Playgroud)

初始化(..)是直接初始化.这需要构造函数调用.在的情况下,arr0arr1,只有复制/移动的构造是可行的.因此,这两个示例意味着从braced-init-list 创建临时std::array,并将其复制/移动到目标.通过复制/移动省略,允许编译器忽略该复制/移动操作,即使它有副作用.

注意,即使临时值是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}});
Run Code Online (Sandbox Code Playgroud)

这些是复制初始化的示例.有两个临时创建:

  • 通过braced-init-list {1, 2, 3}调用复制/移动构造函数
  • 通过表达 std::array<int, 3>(..)

然后将临时的临时复制/移动到指定的目标变量.两个临时工的创造都可以省略.

据我所知,一个实现可以写一个explicit array(array const&) = default;构造函数,而不是违反标准; 这会使这些例子形成不良.([container.requirements.general]排除了这种可能性,对David Krauss表示赞赏,请参阅此讨论.)


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}};
Run Code Online (Sandbox Code Playgroud)

这是聚合初始化.它们都"直接"初始化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})使用复制初始化:参数传递的初始化也是copy-init.但是,对于括号中的缺陷限制,"在形式的声明中T x = { a };",也不允许使用括号来进行论证传递,因为它不是声明,当然也不是那种形式.


关于聚合初始化的主题:

由于约翰内斯绍布指出的评论,它只能保证你可以初始化std::array以下语法[array.overview/2:

array<T, N> a = { initializer-list };

您可以从中推断,如果表单中允许使用大括号T x { a };,则表示语法

array<T, N> a { initializer-list };

形式良好,具有相同的含义.但是,不能保证std::array实际上包含一个原始数组作为其唯一的数据成员(另见LWG 2310).我认为一个例子可能是部分专业化std::array<T, 2>,其中有两个数据成员T m0T m1.因此,人们无法得出结论

array<T, N> a {{ initializer-list }};

结构良好.不幸的是,这导致无法保证初始化std::array临时w/o括号省略的情况T x { a };,并且还意味着奇数示例(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
Run Code Online (Sandbox Code Playgroud)

第一个尝试分别从初始化子句1和子句初始化数组元素2.这种复制初始化相当于foo arr0_0 = 1;其相当于foo arr0_0 = foo(1);非法(删除的复制器).

第二个不包含表达式列表,而是一个初始化器列表,因此它不满足[array.overview]/2的要求.实际上,std::array包含一个原始数组数据成员,它将从第一个初始化子句初始化(仅){1},{2}然后第二个子句是非法的.

第三具有相反的问题,因为第二:它的工作原理,如果有一个数组数据成员,但是没有保证.