Переход на С++ 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, вероятно, будет намного более стабильным, чем просто с деструкторами бросания по старому стандарту.