выбрасывать исключения из деструктора - PullRequest
240 голосов
/ 25 сентября 2008

Большинство людей говорят, что никогда выбрасывает исключение из деструктора - это приводит к неопределенному поведению. Страуструп подчеркивает, что "векторный деструктор явно вызывает деструктор для каждого элемента. Это означает, что, если деструктор элемента выбрасывает, векторное разрушение завершается неудачей ... На самом деле нет хорошего способа защиты от исключений, генерируемых деструкторами, поэтому библиотека не дает никаких гарантий, если деструктор элемента сгенерирует "(из Приложения E3.2) .

Эта статья , похоже, говорит иначе - бросать деструкторы более или менее хорошо.

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

Если во время операции очистки возникает ошибка, вы просто игнорируете ее? Если это ошибка, которая потенциально может быть обработана в стеке, но не в деструкторе, не имеет ли смысла выбрасывать исключение из деструктора?

Очевидно, что такого рода ошибки редки, но возможны.

Ответы [ 16 ]

2 голосов
/ 18 марта 2010

В: Итак, мой вопрос: если бросок из деструктора приводит к неопределенное поведение, как вы справляетесь ошибки, возникающие во время деструктора?

A: Есть несколько вариантов:

  1. Позвольте исключениям вытекать из вашего деструктора, независимо от того, что происходит в другом месте. И при этом помните (или даже опасайтесь), что может последовать std :: terminate.

  2. Никогда не позволяйте исключению вытекать из вашего деструктора. Может быть, напишите в журнал, какой-нибудь большой красный плохой текст, если можете.

  3. my fave : Если std::uncaught_exception вернет false, то возникнут исключения. Если он вернет true, вернитесь к протоколированию.

Но хорошо ли бросать д'торы?

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

Есть несколько странных случаев, когда на самом деле отличная идея бросать из деструктора. Как и код ошибки «должен проверить». Это тип значения, который возвращается из функции. Если вызывающая сторона читает / проверяет содержащийся код ошибки, возвращаемое значение уничтожается молча. Но , если возвращенный код ошибки не был прочитан к тому времени, когда возвращаемые значения выходят из области видимости, он выдаст исключение из своего деструктора .

1 голос
/ 15 апреля 2015

Мартин Ба (выше) на правильном пути - вы по-разному разбираетесь в логике RELEASE и COMMIT.

Для выпуска:

Вы должны есть любые ошибки. Вы освобождаете память, закрываете соединения и т. Д. Никто другой в системе не должен снова ВИДЕТЬ эти вещи, а вы возвращаете ресурсы ОС. Если вам кажется, что вам нужна реальная обработка ошибок, это может быть следствием недостатков дизайна в вашей объектной модели.

Для фиксации:

Здесь вы хотите использовать те же типы объектов-оболочек RAII, что и для мьютексов, такие как std :: lock_guard. С теми, кто не помещает логику коммита в dtor ВСЕ. У вас есть специальный API для него, а затем объекты-обертки, которые RAII передадут его в свои ИХД и обработают там ошибки. Помните, что вы можете ловить исключения в деструкторе просто отлично; его выдача им это смертельно. Это также позволяет вам реализовать политику и другую обработку ошибок, просто создав другую обертку (например, std :: unique_lock и std :: lock_guard), и гарантирует, что вы не забудете вызвать логику фиксации, которая является единственным промежуточным этапом. достойное оправдание для того, чтобы поставить его в 1-м месте.

1 голос
/ 02 февраля 2010

В настоящее время я придерживаюсь политики (о которой так много говорят), что классы не должны активно выбрасывать исключения из своих деструкторов, а вместо этого должны предоставлять открытый метод "close" для выполнения операции, которая может завершиться ошибкой ...

... но я считаю, что деструкторы для классов контейнерного типа, такие как вектор, не должны маскировать исключения, выбрасываемые из классов, которые они содержат. В этом случае я фактически использую метод "free / close", который вызывает себя рекурсивно. Да, я сказал рекурсивно. В этом безумии есть метод. Распространение исключений зависит от наличия стека: если происходит единственное исключение, то оба оставшихся деструктора все еще будут работать, а ожидающее исключение будет распространяться после возвращения подпрограммы, и это здорово. Если возникает несколько исключений, то (в зависимости от компилятора) либо это первое исключение будет распространяться, либо программа завершится, что нормально. Если возникает так много исключений, что рекурсия переполняет стек, тогда что-то серьезно не так, и кто-то собирается узнать об этом, что тоже хорошо. Лично я ошибаюсь в том, что ошибаюсь, вместо того чтобы быть скрытым, тайным и коварным.

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

0 голосов
/ 05 января 2018

Так что мой вопрос таков: если бросок из деструктора приводит к неопределенное поведение, как вы обрабатываете ошибки, которые происходят во время деструктор?

Основная проблема заключается в следующем: вы не можете потерпеть неудачу . Что значит потерпеть неудачу, в конце концов? Если фиксация транзакции в базе данных не удалась и не удалась (не удалось выполнить откат), что произойдет с целостностью наших данных?

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

Это концептуально сложная проблема, но часто решение состоит в том, чтобы просто найти способ убедиться, что сбой не может закончиться неудачей. Например, база данных может записать изменения до фиксации во внешней структуре данных или файле. Если транзакция не удалась, тогда структура файла / данных может быть отброшена. Все, что от него требуется, - убедиться, что фиксация изменений из этой внешней структуры / файла является атомарной транзакцией, которая не может завершиться неудачей.

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

Самое правильное решение для меня - написать логику неочищения таким образом, чтобы логика очистки не могла потерпеть неудачу. Например, если у вас возникнет желание создать новую структуру данных, чтобы очистить существующую структуру данных, возможно, вы захотите заранее создать эту вспомогательную структуру, чтобы нам больше не приходилось создавать ее внутри деструктора.

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

Тем не менее, главная проблема заключается в том, что мы не можем потерпеть неудачу, и это сложная концептуальная проектная проблема, которую нужно идеально решить во всех случаях. Это становится легче, если вы не слишком запутаетесь в сложных управляющих структурах с тоннами маленьких объектов, взаимодействующих друг с другом, и вместо этого смоделируете свои проекты немного громоздким способом (например: система частиц с деструктором, чтобы уничтожить всю частицу система, а не отдельный нетривиальный деструктор на частицу). Когда вы моделируете свои проекты на таком более грубом уровне, у вас есть меньше нетривиальных деструкторов, с которыми вы можете иметь дело, и вы также можете часто позволить себе любую дополнительную нагрузку на память / обработку, чтобы ваши деструкторы не могли выйти из строя.

И это одно из самых простых решений, естественно - реже использовать деструкторы. В приведенном выше примере с частицами, возможно, при уничтожении / удалении частицы, должны быть сделаны некоторые вещи, которые могут потерпеть неудачу по любой причине. В этом случае, вместо того, чтобы вызывать такую ​​логику через dtor частицы, которая может быть выполнена по исключительному пути, вы могли бы вместо этого сделать все это системой частиц, когда удаляет частицу. Удаление частицы всегда может быть сделано во время неисключительного пути. Если система разрушена, возможно, она может просто очистить все частицы и не беспокоиться об этой логике удаления отдельных частиц, которая может дать сбой, тогда как логика, которая может дать сбой, выполняется только во время нормального выполнения системы частиц, когда она удаляет одну или несколько частиц.

Часто бывают решения, подобные тем, которые возникают, если вы избегаете иметь дело с множеством маленьких объектов с нетривиальными деструкторами. Где вы можете запутаться в беспорядке, где кажется почти невозможным быть безопасным от исключений, когда вы запутываетесь в множестве маленьких объектов, у которых все нетривиальные dtors.

Было бы очень полезно, если бы nothrow / noexcept фактически перевелся в ошибку компилятора, если бы что-либо, что его указывает (включая виртуальные функции, которые должны наследовать спецификацию noexcept своего базового класса), попыталось вызвать что-нибудь, что может выдать. Таким образом, мы сможем поймать все эти вещи во время компиляции, если на самом деле напишем деструктор, который может случайно сгенерировать.

0 голосов
/ 27 октября 2013

В отличие от конструкторов, где выдача исключений может быть полезным способом указать, что создание объекта завершилось успешно, исключения не должны создаваться деструкторами.

Проблема возникает, когда из деструктора выдается исключение в процессе разматывания стека. Если это происходит, компилятор оказывается в ситуации, когда он не знает, следует ли продолжить процесс разматывания стека или обработать новое исключение. В результате ваша программа будет немедленно прервана.

Следовательно, лучший способ действий - просто воздерживаться от использования исключений в деструкторах вообще. Вместо этого напишите сообщение в файл журнала.

0 голосов
/ 25 февраля 2013

Установить событие тревоги. Обычно тревожные события являются лучшей формой уведомления о сбое при очистке объектов

...