【问题标题】:Can std::initalizer_list cause lifetime issues?std::initializer_list 会导致生命周期问题吗?
【发布时间】:2014-02-19 15:09:12
【问题描述】:

在使用std::initializer_list时遇到了一些困难。没过多久我就意识到我更多地将它视为一个容器,而实际上它具有引用语义。所以我的问题是,以下哪个示例可能会导致问题,如果不是,为什么它们会起作用? 我应该补充一点,我正在使用 VS2013 并且 std::initializer_list 仅使用开始和结束指针来实现。

示例 1

const auto tmpList = {1, 2, 3};
const vector<int> test(tmpList);

如果文字 1、2 和 3 存储在连续的内存块中,这可能会起作用。但这有保证吗?

示例 2

const string a("foo");
int someOtherVariable = 10;
const string b("bar");
const auto tmpList = {a, b};
const vector<string> test(tmpList);

这应该不起作用,因为 a 和 b 可能在堆栈上的不同位置(请记住 std::initializer_list 只是保留指向第一个字符串的指针)。但话又说回来,编译器应该能够以某种方式处理这个问题,因为这应该在我的理解中起作用:

const vector<string> test({a, b});

是吗?

示例 3

const auto tmpList = {string("foo"), string("bar")};
const vector<string> test(tmpList);

在我看来,初始化列表在传递给向量时指向已经销毁的临时对象。

结论

我认为所有这些例子都表明std::initializer_list 不应该用作临时容器。如果是这种情况,是否应该禁止在任何地方存储初始化列表(作为函数的参数除外)?也许我也只是缺少一些编译器魔术,它确保指针始终指向有效的连续内存。

解决方案和背景

似乎上面所有的例子都定义得很好。我的程序或 VS2013 C++ 编译器中似乎存在错误。当我使用这样的初始化列表时,首先出现了问题:

const auto tmpList = {join(myList1), join(myList2)};
const vector<string> test(tmpList);

join 是一个返回std::string 的函数。在这种情况下,初始化列表包含 2 个条目,但第一个是空的。将其拆分成这样可以解决问题:

const auto str1 = join(myList1);
const auto str2 = join(myList2);
const auto tmpList = {str1, str2};
const vector<string> test(tmpList);

现在我想起来了,它对我来说似乎是一个编译器错误,但它让我相信初始化列表实际上直接存储了指向文字、堆栈变量等的指针,而不是首先将它们复制到本地大批。

【问题讨论】:

  • 所有三种情况都应该可以正常工作(但不要相信我的话,请阅读规范!)。
  • 因为您将值复制到向量中,所以应该没问题。但据说使用initializer_list 作为容器是个坏主意。
  • @JoachimPileborg 好吧,我们已经看到它不起作用,尤其是示例 3。这是有道理的,因为列表仅存储开始和结束指针,并且在分配后将不再存在临时对象(除非有是初始化列表的一些特殊编译器处理)。
  • @Excelcius:你真的看到它不起作用了吗?在这种情况下,您的编译器有问题,并且没有为数组提供与 initializer_list 相同的生命周期。
  • 我也遇到了初始化器列表的编译器问题:stackoverflow.com/questions/20165166/…

标签: c++ c++11 initializer-list object-lifetime


【解决方案1】:

您的所有示例都有明确定义的行为。来自 §8.5.4/5

std::initializer_list&lt;E&gt; 类型的对象是从初始化列表构造的,就好像实现分配了 const E 类型的临时数组 N 元素,其中 N 是初始化列表中的元素数。该数组的每个元素都使用初始值设定项列表的相应元素进行复制初始化,并且构造 std::initializer_list&lt;E&gt; 对象以引用该数组。 ... [ 示例:

 struct X {
X(std::initializer_list<double> v);
};
X x{ 1,2,3 };

初始化将以大致相当于这样的方式实现:

const double __a[3] = {double{1}, double{2}, double{3}};
X x(std::initializer_list<double>(__a, __a+3));

...-结束示例 ]

另外,§8.5.4/6 是相关的

数组与任何其他临时对象 (12.2) 具有相同的生命周期,除了从数组初始化 initializer_list 对象可以延长数组的生命周期,就像将引用绑定到临时对象一样。

该标准之后甚至给出了有效和无效代码的示例。无效的代码示例是这样的:

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

这是无效的,因为为初始化列表创建的临时数组的生命周期在构造函数主体结束时结束,留下 i4 悬空引用。

【讨论】:

  • 谢谢我选择这个作为答案,因为它解释了一个我直到现在才意识到的细节:使用初始化列表时创建了一个隐藏数组。我的问题似乎与编译器有关,请参阅我更新的问题。
  • @Excelcius 这确实是一个编译器错误。请参阅this 问题,以及连接时对应的bug report。和another SO 关于这种行为。
【解决方案2】:

C++11 8.5.4/6 数组的生命周期与initializer_list对象的生命周期相同。

因此,在您的任何示例中都没有生命周期问题。你需要做一些复杂的事情,比如

std::initializer_list<int> bad;
{
    bad = {1,2,3};
}
std::vector<int> test(bad);  // Boom!

遇到问题。

【讨论】:

    猜你喜欢
    • 2013-02-23
    • 1970-01-01
    • 2012-04-28
    • 1970-01-01
    • 2021-09-25
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多