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

时间:2013-12-11 13:08:34

标签: c++ c++11 initialization c++14 stdarray

以下是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标准),正确的是什么? 什么是最常见/最常用的和那些要避免的(以及为什么原因)?

4 个答案:

答案 0 :(得分:19)

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}});

使用(..)进行初始化是直接初始化。这需要构造函数调用。在arr0arr1的情况下,只有复制/移动构造函数是可行的。因此,这两个示例意味着从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}});

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

  • 通过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}};

这是聚合初始化。他们都直接&#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 m0T 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

第一个尝试分别从初始化子句12初始化数组元素。此复制初始化相当于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种形式的数组声明。此外,如果您的编译器没有优化副本,则这些版本的效率会降低。

初始化列表构造函数需要双括号,因此第三行无效。