代码之家  ›  专栏  ›  技术社区  ›  alfC

基于临时范围的循环范围[重复]

  •  7
  • alfC  · 技术社区  · 7 年前

    由于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;
    }
    

    看起来好像 begin end 是从一个临时的并在循环中丢失的。

    当然,一种方法是

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

    然而, 我想知道为什么 for(auto e : f()[5]) 是一个错误,如果有更好的解决方法或某种设计方法 f 甚至是容器( std::vector )为了避免这个陷阱。

    对于迭代器循环,更明显的是为什么会发生这个问题。( 开始 结束 来自不同的临时对象)

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

    但是在for-range循环中,就像在第一个示例中一样,很容易犯这个错误。

    2 回复  |  直到 7 年前
        1
  •  4
  •   songyuanyao    7 年前

    注意,直接使用一个临时的范围表达式是可以的,它的lefetime将被扩展。但是为了 f()[5] 什么 f() 返回是临时的,它是在表达式中构造的,在构造它的整个表达式之后,它将被销毁。

    从C++ 20中,可以使用init语句 range-based for loop 解决这些问题。

    (强调我的)

    如果range表达式返回一个临时表达式,则其生存期将延长 直到循环结束,如绑定到右值所示 参考范围,但是 当心任何暂时的生命 范围内\表达式未扩展 .

    这个问题可以通过使用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;
    
        2
  •  4
  •   Barry    7 年前

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

    我只回答这部分。原因是,基于语句的范围只是对以下内容的语法甜头,大约是:

    {
        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 绑定到该内部引用。但是,在生产线的最后,这个临时设备超出了范围,破坏了它的所有内部结构,并且 _范围 立即是悬空引用。这里没有生存期扩展,我们从不将引用绑定到临时的。

    在更正常的情况下, for(auto e : f()) 我们会绑定 _范围 f() 直接,哪个 将引用绑定到临时对象,以便临时对象的生存期可以延长到引用的生存期,即 for 声明。

    为了增加皱纹,还有其他一些情况,像这样的间接结合仍然可以延长寿命。比如说:

    struct X {
        std::vector<int> v;
    };
    X foo();
    
    for (auto e : foo().v) {
        // ok!
    }
    

    但是,正如宋元耀建议的那样,最好使用带有初始值设定项的新for语句,而不是跟踪所有这些小情况。总是:

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

    虽然这在某种程度上给人一种错误的安全感,因为如果你做了两次,你仍然会有同样的问题…

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