Gre*_*ape 33 c++ lambda initializer-list c++11
考虑一下这个C++ 11代码片段:
#include <iostream>
#include <set>
#include <stdexcept>
#include <initializer_list>
int main(int argc, char ** argv)
{
enum Switch {
Switch_1,
Switch_2,
Switch_3,
Switch_XXXX,
};
int foo_1 = 1;
int foo_2 = 2;
int foo_3 = 3;
int foo_4 = 4;
int foo_5 = 5;
int foo_6 = 6;
int foo_7 = 7;
auto get_foos = [=] (Switch ss) -> std::initializer_list<int> {
switch (ss) {
case Switch_1:
return {foo_1, foo_2, foo_3};
case Switch_2:
return {foo_4, foo_5};
case Switch_3:
return {foo_6, foo_7};
default:
throw std::logic_error("invalid switch");
}
};
std::set<int> foos = get_foos(Switch_1);
for (auto && foo : foos) {
std::cout << foo << " ";
}
std::cout << std::endl;
return 0;
}
Run Code Online (Sandbox Code Playgroud)
无论我尝试什么编译器,所有人似乎都错误地处理它.这让我觉得我做错了,而不是多个编译器的常见错误.
铿锵3.5输出:
-1078533848 -1078533752 134518134
Run Code Online (Sandbox Code Playgroud)
gcc 4.8.2输出:
-1078845996 -1078845984 3
Run Code Online (Sandbox Code Playgroud)
gcc 4.8.3输出(在http://www.tutorialspoint.com上编译):
1 2 267998238
Run Code Online (Sandbox Code Playgroud)
gcc(未知版本)输出(在http://coliru.stacked-crooked.com上编译)
-1785083736 0 6297428
Run Code Online (Sandbox Code Playgroud)
这个问题似乎是由于使用std::initializer_list<int>lambda的返回值引起的.将lambda定义更改为[=] (Switch ss) -> std::set<int> {...}返回值时是正确的.
拜托,帮我解开这个谜.
haa*_*vee 32
来自:http://en.cppreference.com/w/cpp/utility/initializer_list
在原始初始化程序列表对象的生存期结束后,不保证基础数组存在.std :: initializer_list的存储是未指定的(即它可以是自动,临时或静态只读存储器,具体取决于具体情况).
我不认为初始化列表是可复制构造的.std::set和其他容器.基本上,您的代码看起来类似于"返回对临时的引用".
C++ 14与底层存储有一些不同之处 - 延长它的生命周期 - 但这并不能解决任何与initializer_list对象生命周期有关的问题,更不用说它的副本了.因此,即使在C++ 14中,问题仍然存在.
底层数组是一个临时数组,其中每个元素都是从原始初始化列表的相应元素进行复制初始化(除了缩小转换无效).底层数组的生命周期与任何其他临时对象相同,除了从数组初始化initializer_list对象扩展了数组的生命周期,就像绑定对临时的引用一样(具有相同的例外,例如初始化非 - 静态班级成员).底层数组可以在只读存储器中分配.
Sha*_*our 15
问题是您正在引用一个不再存在的对象,因此您正在调用未定义的行为.initializer_list在C++ 11草案标准中似乎没有明确规定,没有规范的部分实际指定了这种行为.虽然有很多笔记表明这不起作用,但总的来说,如果笔记不与规范性文本冲突,则它们不具有规范性,但它们具有很强的指示性.
如果我们转到18.9 初始化列表部分,它会有一条说明:
复制初始化列表不会复制基础元素.
在本节中,8.5.4我们有以下示例:
typedef std::complex<double> cmplx;
std::vector<cmplx> v1 = { 1, 2, 3 };
void f() {
std::vector<cmplx> v2{ 1, 2, 3 };
std::initializer_list<int> i3 = { 1, 2, 3 };
}
Run Code Online (Sandbox Code Playgroud)
以下注释:
对于v1和v2,为{1,2,3}创建的initializer_list对象和数组具有完整表达式生存期.对于i3,initializer_list对象和数组具有自动生命周期.
这些注释与initializer_list提议一致:N2215给出了以下示例:
std::vector<double> v = {1, 2, 3.14};
Run Code Online (Sandbox Code Playgroud)
并说:
现在添加
vector(initializer_list<E>)到vector<E>如上所示.现在,该示例有效.初始化列表{1,2,3.14}被解释为临时构造如下:Run Code Online (Sandbox Code Playgroud)const double temp[] = {double(1), double(2), 3.14 } ; initializer_list<double> tmp(temp, sizeof(temp)/sizeof(double)); vector<double> v(tmp);[...]
请注意,initializer_list是一个小对象(可能是两个单词),因此按值传递它是有意义的.通过值传递还简化了begin()和end()的内联以及size()的常量表达式求值.
初始化器列表将由编译器创建,但可由用户复制.把它想象成一对指针.
在initializer_list这种情况下只是保持指针,这将不离开后范围存在自动变量.
更新
我刚刚意识到该提案实际上指出了这种滥用情况:
一个含义是,initializer_list是"指针式",因为它的行为类似于底层数组的指针.例如:
Run Code Online (Sandbox Code Playgroud)int * f(int a) { int* p = &a; return p; //bug waiting to happen } initializer_list<int> g(int a, int b, int c) { initializer_list<int> v = { a, b, c }; return v; // bug waiting to happen }以这种方式滥用initializer_list实际上需要少量的聪明才智.特别是,initializer_list类型的变量很少见.
我发现最后一句话(强调我的)特别具有讽刺意味.
更新2
因此,缺陷报告1290修复了规范性措辞,因此它现在涵盖了这种行为,尽管副本案例可能更加明确.它说:
当initializer_list是类的非静态数据成员时,会出现关于预期行为的问题.initializer_list的初始化是根据隐式分配的数组的结构来定义的,该数组的生存期"与initializer_list对象的生命周期相同".这意味着只要initializer_list执行就需要生成数组,这表面上看起来需要将数组存储在同一类中的std :: unique_ptr之类的内容中(如果成员在这种方式).
如果这是意图,那将是令人惊讶的,但它会使initializer_list在此上下文中可用.
该决议修正了措辞,我们可以在标准草案的N3485版本中找到新的措辞.所以8.5.4 [dcl.init.list]部分现在说:
该数组与任何其他临时对象(12.2)具有相同的生命周期,除了从数组初始化initializer_- list对象延长了数组的生命周期,就像绑定对临时对象的引用一样.
和12.2 [class.temporary]说:
函数返回语句(6.6.3)中返回值临时绑定的生命周期未扩展; 临时在return语句中的full-expression结束时被销毁.
| 归档时间: |
|
| 查看次数: |
2792 次 |
| 最近记录: |