Ответ 1
Согласно стандарту, это поведение является неправильным, и это уже упоминалось в разделе комментариев вопроса. Об этом говорится в разделе об обработке исключений.
Согласно сообщениям о дефектах на open-std.org, они знали, что реализации (GCC и Clang) ошибались в этом уже в 2015-09-28. Но предлагаемое решение было только в феврале 2016 года, и компиляторы (GCC и Clang) еще не включили это исправление.
Предлагаемое решение (февраль 2016 года):
Изменить пункт 2 пункта 2 статьи 18.2 следующим образом:
Деструктор вызывается для каждого автоматического объекта типа класса, созданного, но еще не уничтоженного, поскольку был введен блок try. Если во время уничтожения временных или локальных переменных для оператора return генерируется исключение (9.6.3 [stmt.return]), также вызывается деструктор для возвращаемого объекта (если есть). Объекты уничтожаются в порядке, обратном завершению их строительства. [Пример:struct A { }; struct Y { ~Y() noexcept(false) { throw 0; } }; A f() { try { A a; Y y; A b; return {}; // #1 } catch (...) { } return {}; // #2 }
На # 1 возвращается объект типа А. Затем локальная переменная b уничтожается (9.6 [stmt.jump]). Затем локальная переменная y уничтожается, вызывая раскручивание стека, что приводит к уничтожению возвращаемого объекта и последующему уничтожению локальной переменной a. Наконец, возвращаемый объект снова создается в # 2. - конец примера]
По этой проблеме были обнаружены ошибки как в GCC, так и в Clang.
Комментарии к сообщению об ошибке GCC указывают, что это явно ошибка.
Джонатан Уэйкли комментирует:
Сейчас 2013 год, поэтому разумно не возвращать по значению, если ваш деструктор может бросить.
И еще один пользователь:
Да, я заметил, и у Clang также была обнаружена ошибка, которая длилась годами. Тем не менее, поведение не так.