unordered_map上的C ++未定义行为在基于范围的for循环中作为右值

问题描述 投票:0回答:1

我讨厌增加关于堆栈溢出时未定义行为的众多问题,但这个问题使我感到迷惑。

当在嵌入式创建的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
        }

}
c++ c++11 for-loop unordered-map
1个回答
3
投票

问题在于,您创建了一个临时std::unordered_map并返回对其内容之一的引用。让我们检查一下这里发生的两种行为:

1。基于范围的扩展:

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返回一个临时对象时,事情会变得更加有趣。我们遇到了终身扩展

2。寿命延长:

这比看起来简单得多。如果您返回一个临时对象以初始化(绑定)对其的引用,则该对象的生存期将延长以匹配所述引用的生存期。这意味着如果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;
}

我们必须承认两件事:

  1. 您创建一个临时的std::unordered_map
  2. range-init不是指您的std::unordered_map。它指的是.at(0)重新调整的内容-该映射中的值。

这将导致以下结果-地图的生存期延长了[[not。这意味着它将在完整表达式的末尾(从;auto && __range = range-init;处)调用其析构函数。调用std::unordered_map::~unordered_map时,它将调用其管理的所有内容的析构函数-例如其键和值。换句话说,该析构函数调用将调用您通过at(0)调用获得引用的向量的析构函数。您的__range不是悬挂的参考。

© www.soinside.com 2019 - 2024. All rights reserved.