为什么在基于范围的初始化程序中使用临时对象会导致崩溃?

kyn*_*tto 51 c++ c++11

为什么以下代码在Visual Studio和GCC上都崩溃了?

要使它崩溃,需要基于范围的for循环,std :: map,std :: string并引用字符串.如果我删除其中任何一个它将工作.

#include <iostream>
#include <string>
#include <map>
using namespace std;

struct S
{
    map<string, string> m;

    S()
    {
        m["key"] = "b";
    }

    const string &func() const
    {
        return m.find("key")->second;
    }
};

int main()
{
    for (char c : S().func())
        cout << c;

    return 0;
}
Run Code Online (Sandbox Code Playgroud)

Ideone链接:http://ideone.com/IBmhDH

Yak*_*ont 62

for(:)循环的范围初始化行不会延长除最终临时(如果有)之外的任何内容的生命周期.在for(:)循环执行之前丢弃任何其他临时值.

现在,不要绝望; 这个问题很容易解决.但首先要了解出了什么问题.

代码for(auto x:exp){ /* code */ }扩展为,基本上:

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

(在__it__end行上有一个适度的谎言,所有以变量开头的变量__都没有可见名称.我也在展示C++ 17版本,因为我相信一个更美好的世界,这里的差异并不重要.)

exp创建一个临时对象,然后返回其中的引用.临时在该行之后死亡,因此您在其余代码中有一个悬空引用.

修复它相对容易.要解决这个问题:

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

当使用临时而不是将引用返回到它们时,执行rvalue重载并按值返回移入的值.

那么

auto&& __range=exp;
Run Code Online (Sandbox Code Playgroud)

line确实引用了返回的by-value的生命周期扩展string,并且没有更多的悬空引用.

作为一般规则,永远不要通过引用可能是rvalue的参数来返回范围.


附录:等待,&&const&之后的方法呢? 右值引用*this

C++ 11添加了右值引用.但this函数的自我参数是特殊的.要根据被调用对象的rvalue/lvalue-ness选择方法的重载,可以使用&&&在方法结束后使用.

这与函数的参数类型非常相似. &&在该方法声明该方法应仅在非const rvalues上调用之后; const&意味着应该调用常量左值.不完全匹配的事情遵循通常的预防规则.

如果有一个方法可以将引用返回到对象中,请确保捕获带有&&重载的临时值,并且在这些情况下不返回引用(返回值)或=delete方法.

  • @Yakk:如果你可以链接到解释方法参数之后的限定符的地方,那将是有教育意义的.大多数人(像我一样)都知道`const`-qualified方法 - 但不是关于`const&` - 或`&&`-限定方法. (2认同)
  • @DonHatch:它会像传统的 for 一样快地失败: `for( const char* p = std::string("abc").c_str(); *p; ++p )` 它并不特定于基于范围的。 (2认同)
  • @BenVoigt这确实是基于范围的:https://en.cppreference.com/w/cpp/language/range-for#Temporary_range_expression“如果range_expression返回临时值,则其生命周期将延长直到循环结束, ...但请注意 range_expression 内任何临时对象的生命周期都不会延长。” 请阅读 http://www.open-std.org/jtc1/sc22/wg21/docs/cwg_close.html 中的问题 900(和 dup 1498),它很好地描述了该问题。我的解释是,仅延长顶级临时的生命周期是一个错误;希望有一天它会得到纠正。 (2认同)

Sam*_*hik 31

S().func()
Run Code Online (Sandbox Code Playgroud)

这构造了一个临时对象,并调用一个方法,该方法返回std::string对临时对象(位于临时对象std::string的一部分的容器中)所拥有(间接)的引用.

获取引用后,临时对象将被销毁.这也会破坏std::string临时对象(间接)拥有的内容.

在此之后,引用对象的任何进一步使用都将变为未定义的行为.例如迭代其内容.

当涉及到使用范围迭代时,这是一个非常常见的陷阱.你真的也因为被绊倒而感到内疚.

  • ...约定的好处是每个人都有自己的约定。这里的约定基本上是在设置范围迭代时避免任何闻起来像临时对象的东西。 (2认同)
  • 正确的。http://en.cppreference.com/w/cpp/language/range-for 的“解释”部分的第一部分基本上给出了范围迭代生成的等效代码。就我而言,当我被这个绊倒时,我被绊倒得很厉害,以至于我永远不会忘记它。 (2认同)
  • @kynnysmatto:范围表达式的值绑定到引用.如果该值恰好是prvalue,那么您将获得终身扩展,但如果不是,则不会. (2认同)