11

Here are 8 ways to declare and initialize arrays in C++11 that seems ok under 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}});

What are the correct ones according to the strict standard (and the upcoming C++14 standard) ? What are the most common/used and those to avoid (and for what reason) ?

4

4 回答 4

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

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

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

  • 通过花括号初始化列表{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}};

这是聚合初始化。它们都“直接”初始化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在评论中指出的那样,只能保证您可以std::array使用以下语法 [array.overview]/2 初始化 a:

数组<T, N> a = {初始化列表};

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

array<T, N> a {初始化列表};

是格式良好的并且具有相同的含义。但是,不能保证std::array实际上包含一个原始数组作为其唯一的数据成员(另请参见LWG 2310)。我认为一个例子可能是部分专业化std::array<T, 2>,其中有两个数据成员T m0T m1. 因此,不能断定

array<T, N> 一个 {{初始化列表}};

格式良好。不幸的是,这会导致无法保证为 初始化std::array临时无括号省略的方法T x { a };,并且也意味着奇数示例 (1, 3, 5, 7) 不需要工作。


所有这些初始化 a 的方法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。这个copy-initialization相当于foo arr0_0 = 1;which又相当于foo arr0_0 = foo(1);which是非法的(deleted copy-ctor)。

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

第三个与第二个有相反的问题:如果有一个数组数据成员,它就可以工作但这不能保证。

于 2013-12-11T13:40:41.417 回答
3

我相信他们都严格遵守,除了可能arr2。我会顺其自然arr3,因为它简洁、清晰且绝对有效。如果arr2是有效的(我只是不确定),那实际上会更好。

组合括号和大括号(0 和 1)对我来说从来都不是很好,等号(4 和 5)是可以的,但我更喜欢较短的版本,而 6 和 7 只是荒谬的冗长。

但是,您可能想采用另一种方式,遵循Herb Sutter 的“几乎总是自动”的风格

auto arr8 = std::array<int, 3>{{1, 2, 3}};
于 2013-12-11T13:14:39.807 回答
1

这个答案链接了一个错误报告,在-Wmissing-braces使用时默认情况下不再启用该报告-Wall。如果打开-Wmissing-bracesgcc会报错 0、2、4 和 6(同clang)。

对于 form 中的语句,允许使用大括号省略,T a = { ... }但不允许T a { }

为什么 std::vector 和 std::array 的 C++ initializer_list 行为不同?

这是詹姆斯麦克内利斯的回答:

但是,这些额外的大括号只能在“T x = { a }; 形式的声明中”被省略 (C++11 §8.5.1/11),即当使用旧样式 = 时。此允许大括号省略的规则不适用于直接列表初始化。这里的脚注是:“在列表初始化的其他用途中不能省略大括号。”

有一个关于此限制的缺陷报告:CWG缺陷 #1270。如果提议的解决方案被采纳,其他形式的列表初始化将允许大括号省略,...

如果采用了提议的解决方案,则其他形式的列表初始化将允许大括号省略,并且以下将是格式良好的:std::array y{ 1, 2, 3, 4 };

Xeo回答是:

...虽然 std::array 没有构造函数,并且 {1, 2, 3, 4} 大括号 init-list 实际上不被解释为 std::initializer_list,而是 std 的内部 C 样式数组的聚合初始化::array(这是第二组大括号的来源:一个用于 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];
}
于 2013-12-11T14:05:40.877 回答
0

最后两个是多余的:您可以在赋值右侧使用 6 种形式的数组声明。此外,如果您的编译器没有优化副本,这些版本的效率就会降低。

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

于 2013-12-11T13:14:57.070 回答