【问题标题】:initializer_list and GCC 4.9.2 vs GCC trunkinitializer_list 和 GCC 4.9.2 与 GCC 主干
【发布时间】:2017-06-26 14:33:31
【问题描述】:

以下是该问题的精简版:

#include <initializer_list>
#include <iostream>

enum objects { zero, one, two, three, four, five, six, seven };

std::initializer_list<objects> objects_list()
{
    return { zero, one, two, three, four, five, six, seven };
}

int main()
{
    for (auto a : objects_list())
    {
        std::cout << a << ' ';
    }
    std::cout << '\n';
}

我的期望是程序输出:

0 1 2 3 4 5 6 7

这是 GCC 4.9.2 所确认的,但是来自其 git 存储库的新 GCC 会产生:

0 0 -85997960 32712 -1076836160 32765 0 32

这看起来基本上是随机数。

我的程序或 GCC 有问题吗?

【问题讨论】:

  • 我从 5 月下旬开始使用 gcc 7.1.1,但无法重现。也许一些新的拉取请求引入了这个错误?
  • 虽然在这里没关系,但当您不需要循环中的副本时,我总是更喜欢for (const auto&amp; a : .....
  • Can reproduce here。很奇怪。
  • std::initializer_list 只是一个数组的“视图”(它是由编译器创建的临时对象,其生命周期由 std::initializer_list 延长,就像引用如何延长暂时的)。它不拥有该阵列。该程序所做的类似于返回对临时的引用。
  • 只是 FWIW,我认为代码在 GCC 4.9.2 中运行良好是 GCC bug 70167 的症状,由 r247793 修复。

标签: c++ c++11 gcc


【解决方案1】:

N4296 § 8.5.4/5 州

std::initializer_list&lt;E&gt; 类型的对象是从初始化列表构造的,就像实现一样 分配了一个由N 类型为const E 的元素组成的临时数组,其中N 是 初始化列表。该数组的每个元素都使用初始化程序的相应元素进行复制初始化 列表,并构造 std::initializer_list&lt;E&gt; 对象以引用该数组

所以我们被告知 std::initializer_list 指的是一个临时数组。

和 § 8.5.4/6 状态

该数组与任何其他临时对象具有相同的生命周期

标准提供了这个例子来证明在数组超出范围后访问初始化列表是未定义的行为:

struct A {
std::initializer_list<int> i4;
A() : i4{ 1, 2, 3 } {} // creates an A with a dangling reference
};

initializer_list 对象在构造函数的 ctor-initializer,所以数组只持续到构造函数退出,所以任何使用 i4 的元素 构造函数退出后会产生未定义的行为。 ——结束示例]

您有一个类似但略有不同的示例,其中涉及复制:

std::initializer_list<objects> objects_list()
{
    return { zero, one, two, three, four, five, six, seven };
}

根据标准的逻辑,数组{zero, one, two, ...} 仅在objects_list 函数的持续时间内持续存在。

18.9/2 [support.initlist] 还支持副本不会持久化底层数组:

复制 [std::]initializer list 不会复制底层元素。

所以我相信你的代码最终是 UB 并且它之前工作的事实是幸运的。

【讨论】:

  • 谢谢。这表明尝试新的编译器总是有回报的,而且这些年来我仍然不懂 C++ ...
  • @cschwan:你和我。这个问题让我学到了关于std::initializer_list 的新知识,为此我感谢你。
  • 不得不说,这种行为实在是太牛了
  • 我不认为 C++17 中的行为与“保证复制省略”一样清晰,因为返回类型是右值,如果我们说 auto x = objects_list(),那么“x”由右值初始化,右值由{ .. } 初始化。我认为这意味着x{ .. } 初始化,这意味着数组仍然存在,因为return 语句和x 的初始化之间没有中间对象。
  • 一个简单的说法是使用std::initializer_list 作为一个想要的容器是一个坏主意。 (我可以这么说是因为我犯了同样的虐待行为,并且遇到了同样的问题。)
猜你喜欢
  • 2020-12-22
  • 2015-10-26
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多