Неопределенное поведение в C ++ для unordered_map как значения в цикле for на основе диапазона - PullRequest
2 голосов
/ 19 октября 2019

Я не хочу добавлять к множеству вопросов о неопределенном поведении при переполнении стека, но эта проблема меня озадачила.

При использовании цикла for на основе диапазона для unordered_map, который создается inline, я получаюнеожиданный результат. При использовании того же цикла в unordered_map, который был впервые назначен переменной, я получаю ожидаемый результат.

Я ожидаю, что оба цикла выведут 1, но это не то, что я наблюдаю.

Любая помощь в понимании происходящего будет принята с благодарностью. Спасибо!

Я работаю на Debian 10 с g ++ 8.3.0

#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
        }

}

1 Ответ

3 голосов
/ 19 октября 2019

Проблема заключается в том, что вы создаете временный std::unordered_map и возвращаете ссылку на одно из его содержимого. Давайте рассмотрим два поведения, которые происходят здесь:

1. На основе диапазона для расширения:

С вопрос, который вы связали в комментариях , мы видим, что следующий синтаксис:

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

Важной частью является понимание того, что когда вы создаете временное значение или предоставляете lvalue, будет создана ссылка на него (auto&& __range). Если мы имеем дело с lvalues, мы приветствуем ожидаемые результаты. Однако когда 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) retuns - значение от этой карты.

Это приводит к следующему следствию - время жизни вашей карты не продлено. Это означает, что его деструктор будет вызываться в конце полного выражения (в ; из auto && __range = range-init;). Когда вызывается std::unordered_map::~unordered_map, он вызывает деструкторы всего, чем управляет - например, его ключи и значения. Другими словами, этот вызов деструктора вызовет деструктор вектора, на который вы получили ссылку посредством вызова at(0). Ваш __range не является висячей ссылкой.

...