为什么在基于范围的初始值设定项中使用临时对象会导致崩溃?

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

为了使它崩溃,它需要基于范围的for循环,std :: map,std :: string并引用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; } 

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

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 */ } } 

(在__it__end行中有一个适中的__it ,所有以__开始的variables都没有可见的名字,而且我正在展示C ++ 17的版本,因为我相信一个更好的世界,这里的差异并不重要。

你的exp创build一个临时对象,然后在其中返回一个引用。 那行后的临时死亡,所以你在代码的其余部分有一个悬而未决的参考。

修复它是相对容易的。 要解决这个问题:

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

在使用临时对象而不是将引用返回给它们时, 通过值来实现rvalue重载和返回值移入。

那么

 auto&& __range=exp; 

行不会引用按值返回的string生命周期扩展,也没有更多的悬挂引用。

作为一般规则,不要通过引用可能是右值的参数来返回范围。


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

C ++ 11增加了右值引用。 但是this或者this函数的自我参数是特殊的。 要根据被调用对象的右值/左值select一个方法的重载,可以在方法结束后使用&&&

这很像一个函数的参数types。 在方法声明该方法只在非const rvalues上被调用后 const&意味着它应该被称为常量左值。 事情不完全匹配按照通常的准则规则。

当你有一个返回一个对象引用的方法时,确保你用&&重载来捕获临时对象,并且在这些情况下不返回一个引用(返回一个值),或者=delete这个方法。

 S().func() 

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

获得引用后,临时对象被破坏。 这也破坏了由临时对象拥有(间接)的std::string

在那之后,引用对象的任何进一步用法变成未定义的行为。 比如遍历其内容。

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