Переход на С++ 11, где деструкторы неявно объявляются без исключения
В С++ 11 деструктор без какой-либо спецификации исключений неявно объявляется с помощью noexcept
, что является изменением с С++ 03. Поэтому код, который использовался для удаления из деструкторов в С++ 03, все равно будет компилироваться в С++ 11, но будет аварийно завершен во время выполнения, как только он попытается выбраться из такого деструктора.
Поскольку с таким кодом не существует ошибки времени компиляции, как можно безопасно переходить на С++ 11, не объявляя всех существующих деструкторов в базе кода как noexcept(false)
, что было бы действительно -вершина и навязчивость или проверка каждого деструктора на предмет потенциального выброса, что было бы действительно трудоемким и подверженным ошибкам делать, или ловить и исправлять все сбои во время выполнения, что никогда не гарантирует, что все такие случаи будут найдены
Ответы
Ответ 1
Обратите внимание, что правила на самом деле не такие жестокие. Деструктор будет неявно noexcept
, если будет объявлен неявно объявленный деструктор. Поэтому маркировка по меньшей мере одного базового класса или типа члена как noexcept (false)
отравит noexcept
всю иерархию/агрегат.
#include <type_traits>
struct bad_guy
{
~bad_guy() noexcept(false) { throw -1; }
};
static_assert(!std::is_nothrow_destructible<bad_guy>::value,
"It was declared like that");
struct composing
{
bad_guy member;
};
static_assert(!std::is_nothrow_destructible<composing>::value,
"The implicity declared d'tor is not noexcept if a member's"
" d'tor is not");
struct inheriting : bad_guy
{
~inheriting() { }
};
static_assert(!std::is_nothrow_destructible<inheriting>::value,
"The d'tor is not implicitly noexcept if an implicitly"
" declared d'tor wouldn't be. An implicitly declared d'tor"
" is not noexcept if a base d'tor is not.");
struct problematic
{
~problematic() { bad_guy {}; }
};
static_assert(std::is_nothrow_destructible<problematic>::value,
"This is the (only) case you'll have to look for.");
Тем не менее, я согласен с Крисом Бекем, что вы рано или поздно должны избавиться от своих деструкторов. Они также могут заставить вашу программу на С++ 98 взорваться в самое неудобное время.
Ответ 2
Как упоминалось 5gon12eder, существуют определенные правила, которые приводят к тому, что деструктор без спецификации исключения неявно объявляется как noexcept
или noexcept(false)
. Если ваш деструктор может бросить, и вы оставите его компилятору, чтобы решить его спецификацию исключения, вы будете играть в рулетку, потому что вы полагаетесь на решение компилятора под влиянием предков и членов класса, а их предки и члены рекурсивно, что слишком сложно отслеживать и может изменяться во время эволюции вашего кода. Поэтому при определении деструктора с телом, который может выбрасывать, и без спецификации исключения, он должен быть явно объявлен как noexcept(false)
. С другой стороны, если вы уверены, что тело не может выбраться, вы можете объявить его noexcept
более явным и помочь оптимизировать компилятор, но будьте осторожны, если вы решите сделать это, потому что если деструктор любой член/предок вашего класса решает бросить, ваш код будет прерван во время выполнения.
Обратите внимание, что любые неявно определенные деструкторы или деструкторы с пустыми телами не создают проблем. Они неявно noexcept
, если все деструкторы всех членов и предков также noexcept
.
Таким образом, лучший способ перейти к переходу - найти всех деструкторов с непустыми телами и не содержать спецификаций исключений и объявить каждый из них, который может быть передан с помощью noexcept(false)
. Обратите внимание, что вам нужно всего лишь проверить тело деструктора - любые его последующие броски или любые броски, выполняемые функциями, которые он вызывает, рекурсивно. Нет необходимости проверять деструкторы с пустым телом, деструкторы с существующей спецификацией исключения или любые неявно определенные деструкторы. На практике не было бы того, что многие из них оставались проверенными, поскольку распространенное использование для них - просто освобождение ресурсов.
Поскольку я отвечаю на себя, это то, что я в конечном итоге сделал в моем случае, и это не было так больно в конце концов.
Ответ 3
Я сам пережил ту же самую дилемму.
В основном, я пришел к выводу, что, принимая тот факт, что эти деструкторы бросают и просто живут с последствиями этого, обычно намного хуже, чем переживать боль, чтобы заставить их не бросать.
Причина в том, что вы рискуете еще более неустойчивыми и непредсказуемыми состояниями, когда вы бросаете деструкторов.
В качестве примера я работал над проектом однажды, когда по разным причинам некоторые разработчики использовали исключения для управления потоками в какой-то части проекта, и он работал отлично в течение многих лет. Позже кто-то заметил, что в другой части проекта иногда клиент не отправлял сетевые сообщения, которые он должен отправлять, поэтому они создали объект RAII, который отправил бы сообщения в свой деструктор. Иногда сетевое взаимодействие генерирует исключение, поэтому этот деструктор RAII будет бросать, но кто заботится правильно? У него нет памяти для очистки, поэтому это не утечка.
И это будет нормально работать в течение 99% времени, за исключением случаев, когда путь управления потоком исключений пересекается с сетью, что также порождает исключение. И тогда у вас есть два живых исключения, которые разматываются сразу, поэтому "bang you're dead", в бессмертных словах С++ FAQ.
Честно говоря, я бы предпочел, чтобы программа немедленно прекратилась, когда деструктор выбрасывает, поэтому мы можем поговорить с тем, кто написал деструктор бросания, чем пытаться поддерживать программу с намеренно метательными деструкторами и что консенсус комитета/сообществу кажется. Таким образом, они сделали это изменение, чтобы помочь вам утверждать, что ваши деструкторы хороши и не бросают. Это может быть много работы, если у вас есть устаревшая кодовая база, но если вы хотите продолжать ее развивать и поддерживать, по крайней мере, на стандарте С++ 11, вам, скорее всего, лучше сделать работу по очистке деструкторы.
Нижняя линия:
Вы правы, вы не можете надеяться гарантировать, что вы найдете все возможные экземпляры деструктора бросания. Таким образом, вероятно, будут некоторые сценарии, где, когда ваш код компилируется на С++ 11, он будет разбиваться в случаях, когда он не будет соответствовать стандарту С++ 98. Но в целом очистка деструкторов и запуск С++ 11, вероятно, будет намного более стабильным, чем просто с деструкторами бросания по старому стандарту.