Why does using a temporary object in the range-based for initializer result in a crash?

后端 未结 2 1059
伪装坚强ぢ
伪装坚强ぢ 2020-12-01 02:33

Why does the following code crash both on Visual Studio and GCC?

For it to crash it requires the range-based for loop, std::map, std::string and taking a reference t

相关标签:
2条回答
  • 2020-12-01 03:15
    S().func()
    

    This constructs a temporary object, and invokes a method that returns a reference to a std::string that's owned (indirectly) by the temporary object (the std::string is in the container that's a part of the temporary object).

    After obtaining the reference, the temporary object gets destroyed. This also destroys the std::string that was owned (indirectly) by the temporary object.

    After that point, any further usage of the referenced object becomes undefined behavior. Such as iterating over its contents.

    This is a very common pitfall, when it comes to using range iteration. Yours truly is also guilty of getting tripped over this.

    0 讨论(0)
  • 2020-12-01 03:29

    The range initialization line of a for(:) loop does not extend lifetime of anything but the final temporary (if any). Any other temporaries are discarded prior to the for(:) loop executing.

    Now, do not despair; there is an easy fix to this problem. But first a walk through of what is going wrong.

    The code for(auto x:exp){ /* code */ } expands to, basically:

    {
      auto&& __range=exp;
      auto __it=std::begin(__range);
      auto __end=std::end(__range);
      for(; __it!=__end;++__it){
        auto x=*__it;
        /* code */
      }
    }
    

    (With a modest lies on the __it and __end lines, and all variables starting with __ have no visible name. Also I am showing C++17 version, because I believe in a better world, and the differences do not matter here.)

    Your exp creates a temporary object, then returns a reference to within it. The temporary dies after that line, so you have a dangling reference in the rest of the code.

    Fixing it is relatively easy. To fix it:

    std::string const& func() const& // notice &
    {
        return m.find("key")->second;
    }
    std::string func() && // notice &&
    {
        return std::move(m.find("key")->second);
    }
    

    do rvalue overloads and return moved-into values by value when consuming temporaries instead of returning references into them.

    Then the

    auto&& __range=exp;
    

    line does reference lifetime extension on the by-value returned string, and no more dangling references.

    As a general rule, never return a range by reference to a parameter that could be an rvalue.


    Appendix: Wait, && and const& after methods? rvalue references to *this?

    C++11 added rvalue references. But the this or self parameter to functions is special. To select which overload of a method based on the rvalue/lvalue-ness of the object being invoked, you can use & or && after the end of the method.

    This works much like the type of a parameter to a function. && after the method states that the method should be called only on non-const rvalues; const& means it should be called for constant lvalues. Things that don't exactly match follow the usual precidence rules.

    When you have a method that returns a reference into an object, make sure you catch temporaries with a && overload and either don't return a reference in those cases (return a value), or =delete the method.

    0 讨论(0)
提交回复
热议问题