请考虑以下示例:
第一个编译单位:
#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编译并运行它,两个片段在一个编译单元中,输出为 &#34;这是字符串1这是字符串2&#34;
当我将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?
BTW:我喜欢Coliru作为在线编译器。有谁知道,如何定义单独的编译单元?答案 0 :(得分:4)
原始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>
消失,它们就会被销毁。也就是说,你只是未定义的行为。为什么事物在一个环境中起作用而另一个环境不起作用并不是很清楚,但这种行为具有未定义行为的性质。
基本的收获是:std::initializer_list<T>
并非真正的一等公民。实质上,它是一种黑客,可以获取一系列堆栈分配的对象,而无需复制它们以允许序列的初始化。它实际上没有位于参数列表之外的地方,它应该能够消耗相同类型的无限数量的参数。