在临时范围内基于范围的for循环

时间:2018-07-20 06:17:05

标签: c++ c++11 temporary-objects c++20 for-range

由于valgrind中存在一些分段错误和警告,我发现该代码不正确,并且在for-range循环中有某种悬空引用。

#include<numeric>
#include<vector>

auto f(){
    std::vector<std::vector<double>> v(10, std::vector<double>(3));
    iota(v[5].begin(), v[5].end(), 0);
    return v;
}

int main(){
    for(auto e : f()[5])
        std::cout << e << std::endl;
    return 0;
}

好像beginend是从临时目录中取出并丢失在循环中的。

当然,一种解决方法是

    auto r = f()[5];
    for(auto e : r)
        std::cout << e << std::endl;

但是,我确实想知道为什么for(auto e : f()[5])是一个错误,以及是否有更好的方法或某种方法来设计f甚至容器(std::vector )以避免这种陷阱。

使用迭代器循环会更清楚地说明发生此问题的原因(beginend来自不同的临时对象)

for(auto it = f()[5].begin(); it != f()[5].end(); ++it)

但是在第一个示例的for-range循环中,似乎很容易犯此错误。

2 个答案:

答案 0 :(得分:4)

请注意,直接使用临时值作为范围表达式是可以的,它将延长其lefetime。但是对于f()[5]来说,f()返回的是临时值,它是在表达式中构造的,并且在构造它的整个表达式之后将被销毁。

从C ++ 20开始,您可以使用range-based for loop的初始化语句来解决此类问题。

(重点是我的)

  

如果range_expression返回一个临时值,则会延长其生存期   直到循环结束,如绑定到右值所示   参考__range,但是请注意,任何临时项的生存期   在range_expression范围内不会扩展

     

使用init语句可以解决此问题:

for (auto& x : foo().items()) { /* .. */ } // undefined behavior if foo() returns by value
for (T thing = foo(); auto& x : thing.items()) { /* ... */ } // OK

例如

for(auto thing = f(); auto e : thing[5])
    std::cout << e << std::endl;

答案 1 :(得分:4)

  

我不知道为什么for(auto e : f()[5])是一个错误

我将回答这一部分。原因是基于范围的for语句只是大约的语法糖:

{
    auto&& __range = f()[5]; // (*)
    auto __begin = __range.begin(); // not exactly, but close enough
    auto __end = __range.end();     // in C++17, these types can be different
    for (; __begin != __end; ++__begin) {
        auto e = *__begin;
        // rest of body
    }
}

看看第一行。怎么了? operator[]上的vector返回对该对象的引用,因此__range绑定到该内部引用。但是随后,临时变量在行尾超出了范围,破坏了其所有内部元素,__range立即成为悬而未决的参考。这里没有生命周期扩展,我们永远不会将引用绑定到临时文件。

在更常见的情况下,for(auto e : f()),我们将__range直接绑定到f(),这 是将对临时对象的引用绑定在一起,这样临时的生存期将延长到引用的生存期,即完整的for语句。

要增加更多的皱纹,在其他情况下,像这样的间接绑定仍会延长使用寿命。像这样说:

struct X {
    std::vector<int> v;
};
X foo();

for (auto e : foo().v) {
    // ok!
}

但是,与其尝试跟踪所有这些小情况,不如像Songyuanyao所建议的那样,始终使用带有构造器的new for语句要好得多……

for (auto&& range = f(); auto e : range[5]) {
    // rest of body
}

尽管这在某种程度上给人一种错误的安全感,因为如果您两次这样做,您仍然会遇到相同的问题...

for (auto&& range = f().g(); auto e : range[5]) {
    // still dangling reference
}