引用的生命周期 / 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 对象而不复制它们以允许初始化序列的技巧。它确实在参数列表之外没有任何位置,参数列表应该能够使用无限数量的相同类型的参数。
考虑以下示例:
第一个编译单元:
#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 对象而不复制它们以允许初始化序列的技巧。它确实在参数列表之外没有任何位置,参数列表应该能够使用无限数量的相同类型的参数。