为什么在基于范围的初始值设定项中使用临时对象会导致崩溃?
为什么下面的代码在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
。
在那之后,引用对象的任何进一步用法变成未定义的行为。 比如遍历其内容。
这是一个非常常见的陷阱,当涉及到使用范围迭代。 你真的也因为这个而被绊倒了。