Назначение в C++ происходит несмотря на исключение на правой стороне

У меня есть некоторый (C++ 14) код, который выглядит следующим образом:

map<int, set<string>> junk;
for (int id : GenerateIds()) {
    try {
        set<string> stuff = GetStuff();
        junk[id] = stuff;
    } catch (const StuffException& e) {
        ...
    }
}

Это работает. Иногда GetStuff() генерирует исключение, которое работает нормально, потому что, если это так, я не хочу, чтобы значение в карте мусора тогда.

Но сначала я написал это в цикле, который не работает:

junk[id] = GetStuff();

Точнее, даже когда GetStuff() генерирует исключение, создается junk[id] (и ему назначается пустой набор).

Это не то, что я ожидал: я ожидал, что они будут функционировать так же.

Есть ли здесь какой-то принцип C++, который я неправильно понял?

Ответ 1

До С++ 17 не было последовательности между left- и правой частью операторов присваивания.

Впервые в С++ 17 было введено явное секвенирование (сначала вычисляется правая часть).

Это означает, что порядок оценки не определен, что означает, что до реализации следует выполнить оценку в том порядке, в котором он хочет, и в этом случае он сначала оценивает сторону left-.

См. Эту ссылку порядка оценки для более подробной информации (особенно пункт 20).

Ответ 2

станд :: Карта :: Оператор []

Возвращает ссылку на значение, которое сопоставлено с ключом, эквивалентным ключу, выполняя вставку, если такой ключ еще не существует.

junk[id] вызывает вышеупомянутую вставку, и после того, как это уже произошло, GetStuff() бросает. Обратите внимание, что в С++ 14 порядок, в котором это происходит, определяется реализацией, поэтому с другим компилятором your junk[id] = GetStuff(); может не выполнить вставку, если GetStuff().

Ответ 3

Вы не понимаете, как operator[] работает на std::map.

Возвращает ссылку на сопоставленный элемент. Поэтому ваш код сначала вставляет элемент по умолчанию в эту позицию, а затем вызывает operator= для установки нового значения.

Чтобы это работало так, как вы ожидаете, вам нужно использовать std::map::insert (*):

junk.insert(std::make_pair(id, GetStuff()));

Предостережение: insert добавит значение только в том случае, если id еще не сопоставлен.