引用的生命周期 / intilializer_list

Lifetime of references / intilializer_list

考虑以下示例:

第一个编译单元:

#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 编译和 运行 这两个片段在一个编译单元中,输出是 "This is string 1 This is string 2"

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

==16368==ERROR: AddressSanitizer: stack-buffer-underflow on address 0x7ffc6602f388 at pc 0x0000006d036a bp 0x7ffc6602f340 sp 0x7ffc6602f338

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

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

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

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

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

基本的 take-away 是这样的:std::initializer_list<T> 并不是真正的第一个 class 公民。从本质上讲,这是一种获取一系列 stack-allocated 对象而不复制它们以允许初始化序列的技巧。它确实在参数列表之外没有任何位置,参数列表应该能够使用无限数量的相同类型的参数。