【问题标题】:Array declaration and initialization in C++11C++11中的数组声明和初始化
【发布时间】:2013-12-11 13:08:33
【问题描述】:

这里有 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 标准),正确的标准是什么? 什么是最常见/最常用的以及应该避免的(以及出于什么原因)?

【问题讨论】:

  • 询问 g++ 时,请使用 -Wall -pedantic。对 Clang 做同样的事情,这很有帮助。例如,arr 4 缺少第二对 {}。
  • 首先,我想知道为什么arr0arr1 还能工作;他们叫move-ctor吗?如果是这样,它们的含义与 2-5 不同(类似于 6 和 7)。
  • 为什么要回滚?版本号很有用,不会分散注意力。
  • @dudeprgm 最佳答案是“示例 0,2,6 不需要工作”。但是,从您的编号中不清楚这些是哪几行。如果没有您的编辑,我会假设“示例 0”表示 arr0,因此您的编号增加了混乱(无论如何对我来说)
  • @MattMcNabb ...哎呀,对不起,我回滚到数字从“示例 1”而不是“示例 0”开始的某个人的早期版本。现在应该修好了。

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


【解决方案1】:

C++11 总结/TL;DR

  • 由于大括号省略缺陷,示例 0、2、6 不需要工作。然而,最新版本的编译器实现了针对该缺陷提出的解决方案,因此这些示例可以正常工作。
  • 因为未指定std::array 是否包含原始数组。因此,示例 1、3、5、7 不需要工作。但是,我不知道在哪些标准库实现中它们不起作用(在实践中)。
  • 示例 4 将始终有效:std::array&lt;int, 3&gt; arr4 = {1, 2, 3};

我更喜欢版本 4 或版本 2(带有大括号省略修复),因为它们直接初始化并且需要/可能工作。

对于 Sutter 的 AAA 样式,您可以使用 auto arrAAA = std::array&lt;int, 3&gt;{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 的情况下,只有复制/移动构造函数是可行的。因此,这两个示例意味着从花括号初始化列表中创建一个临时的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&lt;int, 3&gt;(..)

后一个临时变量然后被复制/移动到命名的目标变量。可以省略两个临时对象的创建。

据我所知,一个实现可以编写一个explicit array(array const&amp;) = 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&lt;int, 3&gt; arr2{1, 2, 3};(示例 0、2、6)格式不正确。据我所知,clang++ 和 g++ 的最新版本已经允许 T x { a }; 中的大括号省略。

在示例 6 中,std::array&lt;int, 3&gt;({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&lt;T, 2&gt;,其中有两个数据成员T m0T 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

第一个尝试分别从初始化子句 12 初始化数组元素。此复制初始化等效于foo arr0_0 = 1;,后者又等效于非法的foo arr0_0 = foo(1);(已删除copy-ctor)。

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

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

【讨论】:

  • 如果您指出示例 1、3、5 和 7 不能保证也能正常工作(因为它们依赖于内部聚合结构/深度),我会赞成。
  • @JohannesSchaub-litb 嗯.. 是的.. 这很糟糕。我重组了我的答案,并包括了我对您评论的理解(这导致了两个不幸的结论)。您是否碰巧知道是否有任何不使用原始数组成员的实现?
【解决方案2】:

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

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

但是,您可能想采用另一种方式,遵循Herb Sutter's "almost always auto" style

auto arr8 = std::array<int, 3>{{1, 2, 3}};

【讨论】:

  • 我正在编写一个低级且高度模板化的库,因此我尽可能避免使用关键字“auto”,因为我需要跟踪类型(但我同意在大多数情况下,它可以成为一个解决方案)。
  • "auto" 以这种方式使用看起来就像编写 Python 代码,其中声明的变量的类型由分配的对象知道。在这个例子中使用它并不算太糟糕,但我更喜欢初始帖子的第三个构造函数。对于循环中声明的迭代器,我主要使用“auto”。
  • @dyp 的回答似乎是说 std::array&lt;int, 3&gt;{{1, 2, 3}}; 不能保证工作,因为未指定 std::array 是否在内部使用 C 样式数组(因此 {1,2,3} 可能无法正确初始化无论它在内部使用什么)
【解决方案3】:

answer 链接一个bug report,其中-Wmissing-braces 在使用-Wall 时默认不再启用。如果开启-Wmissing-bracesgcc会报错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];
}

【讨论】:

    【解决方案4】:

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

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

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2021-04-15
      • 2011-06-26
      • 2018-05-20
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多