【问题标题】:Lifetime of references / intilializer_list引用的生命周期 / intilializer_list
【发布时间】:2016-04-11 12:50:41
【问题描述】:

考虑以下示例:

第一个编译单元:

#include <vector>
#include <string>
#include <initializer_list>
#include <iostream>

struct DoubleString
{
  std::string one;
  std::string two;
};

class E
{
  public:
  E(std::initializer_list<DoubleString> init) : stringVec(std::move(init))
  {}

  void operator()()
  {
    for (auto const & x : stringVec)
    {
      std::cout << x.one << " " << x.two << std::endl;
    }
  }

  private:
    std::initializer_list<DoubleString> stringVec;
};

class F
{
  public:
    F( const std::string & one, const std::string & two) : e{ {one, two} }
    { }

    void operator()()
    {
      e();
    }

  private:
    E e;
};

class Caller
{
  public:
    void operator[](F f);
};


int main()
{
  Caller()[ F{"This is string 1", "This is string 2"} ];
}

单独的编译单元:

void Caller::operator[](F f)
{
  f();
}

另见http://coliru.stacked-crooked.com/a/b01d349fa8f22f62

使用 gcc 和 clang 编译和运行它,在一个编译单元中使用 sn-ps,输出是 "这是字符串 1 这是字符串 2"

当我将 void Caller::operator [](F f) 移动到单独的编译单元时,它仍然适用于 gcc,但会中断 clang(它打印垃圾)。 Clang 地址清理程序检测到:

==16368==错误:AddressSanitizer:堆栈缓冲区下溢地址 0x7ffc6602f388 在 pc 0x0000006d036a bp 0x7ffc6602f340 sp 0x7ffc6602f338

当我使用 std::vector 作为变量 E::stringVec 的类型时,它再次适用于 clang。

看来我误用了 std::initializer_list。是否允许将其用作变量?为什么它适用于 gcc 而不适用于 clang?

顺便说一句:我喜欢 Coliru 作为在线编译器。有谁知道,如何定义单独的编译单元?

【问题讨论】:

  • 能否请您显示非工作代码,即当代码被拆分为不同的头文件和源文件时?
  • 来自en.cppreference.com/w/cpp/utility/initializer_list。在原始初始化列表对象的生命周期结束后,不能保证底层数组存在。 std::initializer_list 的存储是未指定的(即它可以是自动、临时或静态只读内存,具体取决于具体情况)。
  • 这就像你正在处理一个指向堆栈中某处的悬空指针。
  • @JoachimPileborg:我编辑了这篇文章。希望有帮助
  • 同样来自 kukyakya 上面提供的链接 “初始化器列表可以实现为一对指针或指针和长度。复制 std::initializer_list 不会复制底层对象。”.

标签: c++ c++11 g++ initializer-list clang++


【解决方案1】:

原始的std::initializer_list&lt;DoubleString&gt; 是作为F 的构造函数调用的一部分创建的(当将其传递给成员e 时)。创建此std::initializer_list&lt;DoubleString&gt; 时,会创建DoubleString 对象。 std::initializer_list&lt;DoubleString&gt;DoubleString 对象的生命周期在 e 成员完成初始化时结束。

但是,std::initializer_list&lt;T&gt; 并不是真正的值类型。它是可复制的,但该副本不会创建副本,而只是将堆栈指针复制到用于创建std::initializer_list&lt;T&gt; 的对象。 E 中的副本实际上指向一系列对象(嗯,只有一个),一旦原始 st::initializer_list&lt;DoubleString&gt; 消失,这些对象就会被销毁。也就是说,您只是未定义的行为。为什么事情在一种环境中起作用而不在另一种环境中起作用还不是很清楚,但这是未定义行为的本质。

基本要点是:std::initializer_list&lt;T&gt; 并不是真正的一等公民。从本质上讲,它是一种获取堆栈分配对象序列而不复制它们以允许序列初始化的技巧。它确实在参数列表之外没有位置,应该能够使用无限数量的相同类型的参数。

【讨论】:

    猜你喜欢
    • 2019-12-18
    • 2015-11-18
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2014-06-29
    • 1970-01-01
    相关资源
    最近更新 更多