通过右值容器迭代

时间:2014-02-10 14:40:57

标签: 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;
}

如果未定义,修复它的好方法是什么?如果我使用c ++ 11 range-for循环而不是BOOST_FOREACH怎么办?

3 个答案:

答案 0 :(得分:3)

不幸的是,这很可能是未定义的行为。

问题在于你有两个级别:

  1. std::map<...>是一个r值,它的生命周期将扩展到完整表达式结束
  2. std::vector<int>&是l值引用(进入对象),其生命周期是对象的生命周期。
  3. 问题出现是因为代码(大致)扩展为类似:

    // from
    for (<init>: <expr>) {
        <body>
    }
    
    // to
    auto&& __container = <expr>;
    for (auto __it = begin(container), __e = end(container); __it != __e; ++__it)
    {
        <init> = *__it;
        <body>
    }
    

    这里的问题在于__container的初始化:

    auto&& __container = foo()[42];
    

    如果它仅在foo(),那么这会有效,因为std::map<...>的生命周期会延长到与__container相匹配,但在这种情况下,我们得到:

    // non-standard gcc extension, very handy to model temporaries:
    std::vector<int>& __container = { std::map<...> m = foo(); m[42] };
    

    因此__container最终指向幽冥。

答案 1 :(得分:2)

返回值一直存在,直到完整表达式结束 创造它。所以这一切都取决于BOOST_FOREACH的方式 扩展;如果它在for循环之外创建一个范围,并且 将返回值复制到其中的变量(或使用它 初始化参考),那么你是安全的。如果没有, 你不是。

C ++ 11 range-for循环基本上具有绑定的语义 在经典for循环之外的范围内的引用,所以它 应该是安全的。

编辑:

如果您正在捕获的返回值,这将适用 foo。正如本杰明林德利指出的那样,你不是。你是 在地图上捕获operator[]的返回值。还有这个 是不是临时的;这是一个参考。所以没有延伸 生命周期发生,既不在BOOST_FOREACH也不在范围内。 这意味着地图本身将在最后被破坏 包含函数调用的完整表达式,以及 发生未定义的行为。 (我想,Boost可以做得很深 地图的副本,所以你是安全的。但不知何故,我怀疑它 一样。)

编辑结束:

从来没有,我会怀疑回归的智慧 std::map当你想要的只是一个条目时。如果 map实际上存在于函数外部(不在堆上), 然后我会返回它的引用。否则,我会找到一些 它做了什么。

答案 2 :(得分:0)

来自:http://www.boost.org/doc/libs/1_55_0/doc/html/foreach.html

  

迭代一个按值返回序列的表达式(即一个右值):

extern std::vector<float> get_vector_float();
BOOST_FOREACH( float f, get_vector_float() )
{
    // Note: get_vector_float() will be called exactly once
}

因此它定义明确且有效。

同样,它在C ++ 11(并且有效)中得到了很好的定义:

for (const int& i : get_vector()) // get_vector() computed only once
{
    std::cout << i << std::endl;
}

这里的问题是foo()[42]从临时(通过方法)返回引用

auto& v = foo()[42];

foo()临时的生命没有延长......

您可以通过延长foo临时生命期

来解决这个问题
auto&& m = foo();

for (const int& i : m[42]) {
    std::cout << i << std::endl;
}