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

C++ 为什么在基于范围的初始值设定项中使用临时对象会导致崩溃?,c++,c++11,C++,C++11,为什么以下代码在VisualStudio和GCC上都会崩溃 要使其崩溃,它需要基于范围的For循环、std::map、std::string和对字符串的引用。如果我移除其中任何一个,它都会起作用 #include <iostream> #include <string> #include <map> using namespace std; struct S { map<string, string> m; S() {

为什么以下代码在VisualStudio和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;
}
#包括
#包括
#包括
使用名称空间std;
结构
{
地图m;
S()
{
m[“键”]=“b”;
}
常量字符串&func()常量
{
返回m.find(“key”)->second;
}
};
int main()
{
对于(char c:S().func())
库特
这将构造一个临时对象,并调用一个方法,该方法返回对临时对象(间接)拥有的
std::string
的引用(该
std::string
位于作为临时对象一部分的容器中)

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

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

这是一个非常常见的陷阱,当涉及到使用范围迭代时。你真的很抱歉被它绊倒了

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

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

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


在使用范围迭代时,这是一个非常常见的陷阱。您的用户也会因此而感到内疚。

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 */
  }
}
(在
\uuu it
\uu end
行上有一个适度的谎言,所有以
\uuuu
开头的变量都没有可见的名称。此外,我展示的是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);
}
在使用临时表时,不要执行右值重载并按值返回移动到的值,而不要将引用返回到临时表中

然后

auto&& __range=exp;
行引用了返回的by值
字符串的生存期扩展,不再有悬空引用

一般来说,永远不要通过引用可能是右值的参数来返回范围


附录:等待,
&
常量&
在方法之后

C++11添加了右值引用。但是函数的
this
或self参数是特殊的。要根据被调用对象的右值/左值选择方法的重载,可以在方法结束后使用
&
&

这与函数的参数类型非常相似。
&
在该方法声明只应在非常量值上调用该方法之后;
常量&
表示应为常量左值调用该方法。不完全匹配的情况遵循通常的精度规则


当您有一个将引用返回到对象中的方法时,请确保捕获带有
&
重载的临时对象,并且在这些情况下不返回引用(返回值),或者
=删除该方法。

针对(:)的
的范围初始化行
循环只会延长最终临时变量(如果有)的生存期。在(:)的
循环执行之前,会丢弃任何其他临时变量

现在,不要绝望;这个问题有一个简单的解决方法。但首先,我们来看看哪里出了问题

(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 */
  }
}
(在
\uuu it
\uu end
行上有一个适度的谎言,所有以
\uuuu
开头的变量都没有可见的名称。此外,我展示的是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);
}
在使用临时表时,不要执行右值重载并按值返回移动到的值,而不要将引用返回到临时表中

然后

auto&& __range=exp;
行引用了返回的by值
字符串的生存期扩展,不再有悬空引用

一般来说,永远不要通过引用可能是右值的参数来返回范围


附录:等待,
&
常量&
在方法之后

C++11添加了右值引用。但是函数的
this
或self参数是特殊的。要根据被调用对象的右值/左值选择方法的重载,可以在方法结束后使用
&
&

这与函数的参数类型非常相似。
&&
在方法声明该方法应