我讨厌增加关于堆栈溢出时未定义行为的众多问题,但这个问题使我感到迷惑。
当在嵌入式创建的unordered_map上使用基于范围的for循环时,出现意外结果。在首先分配给变量的unordered_map上使用相同的循环时,我得到了预期的结果。
我希望两个循环都能打印出1,但这不是我观察到的。
任何帮助您了解正在发生的事情的人,将不胜感激。谢谢!
我正在使用g ++ 8.3.0在Debian 10上运行
#include <algorithm>
#include <unordered_map>
#include <iostream>
#include <vector>
int main() {
for (const int i : std::unordered_map<int, std::vector<int>> {{0, std::vector<int> {1}}}.at(0)) {
std::cout << i << std::endl; //prints 0
}
std::unordered_map<int, std::vector<int>> map {
{0, std::vector<int> {1}}
};
for (const int i : map.at(0)) {
std::cout << i << std::endl; //prints 1
}
}
问题在于,您创建了一个临时std::unordered_map
并返回对其内容之一的引用。让我们检查一下这里发生的两种行为:
从the question you linked in the comments我们可以看到以下语法:
for ( for-range-declaration : expression ) statement
直接翻译为以下内容:
{
auto && __range = range-init;
for ( auto __begin = begin-expr,
__end = end-expr;
__begin != __end;
++__begin ) {
for-range-declaration = *__begin;
statement
}
}
重要的部分是要了解,当您创建一个临时值或提供一个左值时,将为其创建一个引用(auto&& __range
)。如果我们正在处理左值,那么我们会掩盖我们期望的结果。但是,当range-init
返回一个临时对象时,事情会变得更加有趣。我们遇到了终身扩展。
这比看起来简单得多。如果您返回一个临时对象以初始化(绑定)对其的引用,则该对象的生存期将延长以匹配所述引用的生存期。这意味着如果range-init
返回一个临时变量,则保存对它的引用(__range
)可以将该临时变量的生存期延长到我上面复制粘贴的代码的最后一个大括号。这就是那些最外面的括号的原因。
在您的情况下,我们的处境非常棘手。检查循环:
for (const int i : std::unordered_map<int, std::vector<int>> {{0, std::vector<int> {1}}}.at(0)) {
std::cout << i << std::endl;
}
我们必须承认两件事:
std::unordered_map
。range-init
不是指您的std::unordered_map
。它指的是.at(0)
重新调整的内容-该映射中的值。这将导致以下结果-地图的生存期延长了[[not。这意味着它将在完整表达式的末尾(从;
的auto && __range = range-init;
处)调用其析构函数。调用std::unordered_map::~unordered_map
时,它将调用其管理的所有内容的析构函数-例如其键和值。换句话说,该析构函数调用将调用您通过at(0)
调用获得引用的向量的析构函数。您的__range
不是悬挂的参考。