C++ 在右值容器中迭代
以下代码是否导致未定义的行为C++ 在右值容器中迭代,c++,loops,c++11,rvalue,boost-foreach,C++,Loops,C++11,Rvalue,Boost Foreach,以下代码是否导致未定义的行为 std::map<int, vector<int>> foo() { return ... } BOOST_FOREACH(const int& i, foo()[42]) { std::cout << i << std::endl; } std::map foo() { 返回。。。 } BOOST_FOREACH(const int&i,foo()[42]) { 标准::cout来自: 迭代按值返回序列(
std::map<int, vector<int>> foo()
{
return ...
}
BOOST_FOREACH(const int& i, foo()[42])
{
std::cout << i << std::endl;
}
std::map foo()
{
返回。。。
}
BOOST_FOREACH(const int&i,foo()[42])
{
标准::cout来自:
迭代按值返回序列(即右值)的表达式:
foo()
temporary的寿命没有延长
您可以通过延长foo
临时生存期来解决这个问题
auto&& m = foo();
for (const int& i : m[42]) {
std::cout << i << std::endl;
}
auto&&m=foo();
for(const int&i:m[42]){
std::cout返回值一直存在,直到完整表达式结束
这就产生了它。所以这一切都取决于如何BOOST\u FOREACH
扩展;如果它在for循环之外创建了一个作用域,则
将返回值复制到其中的变量(或使用它
初始化一个引用),那么你就安全了。如果它不安全,
你不是
循环的C++11范围基本上具有绑定的语义
在经典for循环之外的作用域中引用,因此
应该是安全的
编辑:
如果您捕获的是
foo
。正如本杰明·林德利指出的那样,你不是。你是
在地图上捕获操作符[]
的返回值。这
不是临时的;它是引用。因此
寿命发生,既不在每小时的升压范围内,也不在的范围内。
这意味着地图本身将在游戏结束时被销毁
包含函数调用的完整表达式,以及
未定义的行为会发生。(我想Boost可能会产生深刻的影响。)
地图的副本,这样你就安全了。但不知怎的,我怀疑它
是的。)
编辑结束:
尽管如此,我还是会质疑退回一张支票是否明智
std::map
当您只需要其中的一个条目时。如果
映射实际上存在于函数外部(不在堆上),
然后我会返回一个引用,否则我会找到一些
不幸的是,这很可能是未定义的行为
问题是这里有两个级别:
std::map
是一个r值,其生存期将扩展到完整表达式的结尾
std::vector&
是一个l值引用(指向一个对象),它的生命周期是该对象的生命周期
出现问题的原因是代码(大致)扩展为:
// from
for (<init>: <expr>) {
<body>
}
// to
auto&& __container = <expr>;
for (auto __it = begin(container), __e = end(container); __it != __e; ++__it)
{
<init> = *__it;
<body>
}
如果它只是foo()
,这将起作用,因为std::map
的生存期将被延长以匹配\uu容器的生存期,但是在这种情况下,我们得到:
// non-standard gcc extension, very handy to model temporaries:
std::vector<int>& __container = { std::map<...> m = foo(); m[42] };
//非标准gcc扩展,非常方便建模临时对象:
std::vector&u container={std::map m=foo();m[42]};
因此,\uu容器
最终指向下面。为什么你认为这里会有UB?我在类似的代码中看到内存损坏。但不确定是否是因为这种用法。请查看。似乎有一些宏魔法可以检测参数是否为右值,在这种情况下,它会复制参数。@user2079303:unform单独地,std::map::operator[]
返回一个l值引用(到一个临时对象中);而不是一个r值;因此map
temporary的生存期没有正确地延长,我们最终得到了一个到虚空的引用。他所做的事情并不简单。operator[]
在映射上,它不是按值返回,而是返回一个引用。你确定它应该在循环的范围内工作吗?@BenjaminLindley标准似乎是这样说的。在§6.5.4中,它给出了基于范围的等价代码,并且在那里,初始化表达式绑定到引用,因此它的生存期应该延长以匹配t引用的帽子。(当然,考虑到这是一个新特性,我不相信任何编译器都能100%正确地完成它。)@BenjaminLindley,但正如您在对另一个答案的评论中指出的,我刚刚读到的,他不是用函数的返回值初始化引用;他是用[]的返回值初始化引用
在地图上。由于这会返回一个引用(而不是临时引用),没有寿命的延长。很好,我错过了。你能肯定地说它总是未定义的吗?@balki:不,尽管我没有多少疑问。据我所知,可以改进的范围来处理这个问题——因为标准中提供的转换是非规范性的,编译器知道我所知道的所有临时变量然而,我现在看到了Boost如何处理这个问题,因为类型系统没有足够的表达能力来反映这个问题。6.5.4/1中的转换是规范性的,除了只显示变量的名称\uu range
,\uu begin
和\uu end
。我不相信编译器可以删除e此处未定义的行为不一定违反临时变量的生存期规则-因此这充其量只是一个不符合要求的扩展。似乎需要更改核心语言才能将
中创建的任何临时变量的生存期延长到整个循环体。@Casey:啊,感谢您的澄清。是的,显然是必须对任何临时设备的使用寿命进行规范,否则将存在可移植性问题。
auto&& m = foo();
for (const int& i : m[42]) {
std::cout << i << std::endl;
}
// from
for (<init>: <expr>) {
<body>
}
// to
auto&& __container = <expr>;
for (auto __it = begin(container), __e = end(container); __it != __e; ++__it)
{
<init> = *__it;
<body>
}
auto&& __container = foo()[42];
// non-standard gcc extension, very handy to model temporaries:
std::vector<int>& __container = { std::map<...> m = foo(); m[42] };