C++ lambda返回初始值设定项列表中的奇怪值
考虑这段C++11代码片段:C++ lambda返回初始值设定项列表中的奇怪值,c++,c++11,lambda,initializer-list,C++,C++11,Lambda,Initializer List,考虑这段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_XXX
#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;
}
gcc 4.8.2输出:
-1078533848 -1078533752 134518134
-1078845996 -1078845984 3
gcc 4.8.3输出(编译日期):
gcc(未知版本)输出(在上编译)
这个问题似乎是使用std::initializer\u list
作为lambda的返回值引起的。将lambda定义更改为[=](开关ss)->std::set{…}
时,返回的值是正确的
请帮我解开这个谜团。来自:
在原始初始值设定项列表对象的生存期结束后,不能保证基础数组存在。std::initializer_列表的存储未指定(即,它可以是自动、临时或静态只读内存,具体取决于具体情况)
我不认为初始值设定项列表是可复制的<代码>标准::设置和其他容器。基本上,代码的行为类似于“返回对临时对象的引用”
对于底层存储,C++14有一些略微不同的说法——延长其生命周期——但这并不能解决与
初始值设定项列表
对象的生命周期有关的任何问题,更不用说它的副本了。因此,即使在C++14中,问题仍然存在
底层数组是一个临时数组,其中每个元素都是从原始初始值设定项列表的相应元素复制初始化的(缩小转换无效除外)。基础数组的生存期与任何其他临时对象的生存期相同,不同之处在于,从数组初始化初始值设定项\u list对象会延长数组的生存期,就像将引用绑定到临时对象一样(但也有相同的例外,例如初始化非静态类成员)。可以在只读内存中分配基础阵列
问题是您正在引用一个不再存在的对象,因此您正在调用<代码>初始值设定项列表在中似乎没有明确规定,没有任何规范性章节实际规定此行为。尽管有大量的注释表明这不起作用,而且一般来说,尽管注释不是规范性的,但如果它们与规范性文本不冲突,那么它们具有强烈的指示性 如果我们转到
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 };
}
实际上,误用一个文件只需要少量的创意
初始值设定项以这种方式列出。特别是,类型为
初始值设定项列表将很少见
我觉得最后一句话(我的重点)特别具有讽刺意味
更新2
因此,修正了规范性措辞,因此它现在涵盖了这种行为,尽管复制案例可能更加明确。它说:
当初始值设定项列出时,出现了一个有关预期行为的问题
是类的非静态数据成员。初始化
初始值设定项列表是根据
隐式分配的数组,其生存期“与
初始值设定项“列表对象”。这意味着阵列需要存活
只要初始值设定项_列表存在,它就会出现在它的表面上
似乎需要将数组存储在类似
std::同一类中唯一的\u ptr(如果成员是
以这种方式初始化)
如果这是目的的话,那将是令人惊讶的,但这将使
初始值设定项\u列表在此上下文中可用
决议修正了措辞,我们可以在报告中找到新的措辞。因此,第8.5.4节[dcl.init.list]现在说:
该数组与任何其他临时对象(12.2)具有相同的生存期,
除了初始化数组中的初始值设定项\列表对象
扩展数组的生存期,就像将引用绑定到
临时的
而12.2
[class.temporary]则表示:
临时绑定到函数中返回值的生存期
返回语句(6.6.3)未扩展;临时文件被销毁
在return语句中完整表达式的末尾
因此,
initializer\u list
s在自身被复制或移动到复制/移动结果时不会延长其引用数组的生存期。这使得退回它们成了问题。(它们确实会将引用数组的生存期延长到它们自己的生存期,但此扩展不会传递到省略或列表副本)
要解决此问题,请存储数据并手动管理其生存期:
template<size_t size, class T>
std::array<T, size> partial_array( T const* begin, T const* end ) {
std::array<T, size> retval;
size_t delta = (std::min)( size, end-begin );
end = begin+delta;
std::copy( begin, end, retval.begin() );
return retval;
}
template<class T, size_t max_size>
struct capped_array {
std::array<T, max_size> storage;
size_t used = 0;
template<size_t osize, class=std::enable_if_t< (size<=max_size) >>
capped_array( std::array<T, osize> const& rhs ):
capped_array( rhs.data(), rhs.data()+osize )
{}
template<size_t osize, class=std::enable_if_t< (size<=max_size) >>
capped_array( capped_array<T, osize> const& rhs ):
capped_array( rhs.data(), rhs.data()+rhs.used )
{}
capped_array(capped_array const& o)=default;
capped_array(capped_array & o)=default;
capped_array(capped_array && o)=default;
capped_array(capped_array const&& o)=default;
capped_array& operator=(capped_array const& o)=default;
capped_array& operator=(capped_array & o)=default;
capped_array& operator=(capped_array && o)=default;
capped_array& operator=(capped_array const&& o)=default;
// finish-start MUST be less than max_size, or we will truncate
capped_array( T const* start, T const* finish ):
storage( partial_array(start, finish) ),
used((std::min)(finish-start, size))
{}
T* begin() { return storage.data(); }
T* end() { return storage.data()+used; }
T const* begin() const { return storage.data(); }
T const* end() const { return storage.data()+used; }
size_t size() const { return used; }
bool empty() const { return !used; }
T& front() { return *begin(); }
T const& front() const { return *begin(); }
T& back() { return *std::prev(end()); }
T const& back() const { return *std::prev(end()); }
capped_array( std::initializer_list<T> il ):
capped_array(il.begin(), il.end() )
{}
};
你的代码也能工作。未使用空闲存储(无堆分配)
更高级的版本将使用一个未初始化的数据数组,并手动构造每个
T
是的,这就是实际情况。init列表由堆栈分配的数组支持,当lambda返回时,该数组将失效。initializer\u list
是可复制的(因此可以编译),但它只执行浅层复制。坦率地说,我发现这是一个糟糕的C++11“特性”。幸运的是,这在C++14中是固定的,在C++14中,基础数组的生存期在初始值设定项列表
的副本期间延长,这与将其绑定到引用时的情况非常相似。不幸的是,GCC4.9.2在C++14模式下运行。我没有用头测试过。这当然是真的。这不是一个非常有用的功能;-)“幸运的是,这个‘疏忽’可以/应该在C++14中修复”,您粘贴的段落中的哪句话表明这应该修复,这是一个疏忽“基础数组的生存期与任何其他临时对象相同,只是从数组初始化初始值设定项\列表对象会延长数组的生存期,就像将引用绑定到临时对象一样。”创建引用
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 };
}
std::vector<double> v = {1, 2, 3.14};
const double temp[] = {double(1), double(2), 3.14 } ;
initializer_list<double> tmp(temp,
sizeof(temp)/sizeof(double));
vector<double> v(tmp);
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
}
template<size_t size, class T>
std::array<T, size> partial_array( T const* begin, T const* end ) {
std::array<T, size> retval;
size_t delta = (std::min)( size, end-begin );
end = begin+delta;
std::copy( begin, end, retval.begin() );
return retval;
}
template<class T, size_t max_size>
struct capped_array {
std::array<T, max_size> storage;
size_t used = 0;
template<size_t osize, class=std::enable_if_t< (size<=max_size) >>
capped_array( std::array<T, osize> const& rhs ):
capped_array( rhs.data(), rhs.data()+osize )
{}
template<size_t osize, class=std::enable_if_t< (size<=max_size) >>
capped_array( capped_array<T, osize> const& rhs ):
capped_array( rhs.data(), rhs.data()+rhs.used )
{}
capped_array(capped_array const& o)=default;
capped_array(capped_array & o)=default;
capped_array(capped_array && o)=default;
capped_array(capped_array const&& o)=default;
capped_array& operator=(capped_array const& o)=default;
capped_array& operator=(capped_array & o)=default;
capped_array& operator=(capped_array && o)=default;
capped_array& operator=(capped_array const&& o)=default;
// finish-start MUST be less than max_size, or we will truncate
capped_array( T const* start, T const* finish ):
storage( partial_array(start, finish) ),
used((std::min)(finish-start, size))
{}
T* begin() { return storage.data(); }
T* end() { return storage.data()+used; }
T const* begin() const { return storage.data(); }
T const* end() const { return storage.data()+used; }
size_t size() const { return used; }
bool empty() const { return !used; }
T& front() { return *begin(); }
T const& front() const { return *begin(); }
T& back() { return *std::prev(end()); }
T const& back() const { return *std::prev(end()); }
capped_array( std::initializer_list<T> il ):
capped_array(il.begin(), il.end() )
{}
};
auto get_foos = [=] (Switch ss) -> capped_array<int,3> {
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");
}
};