Подтвердить что ты не робот

Какие операции не должны вызывать исключение в С++?

Сегодня я узнал, что swap не разрешено вызывать исключение в С++.

Я также знаю, что следующие исключения не могут генерировать исключения:

  • деструкторы
  • Чтение/запись примитивных типов

Есть ли другие?
Или, может быть, есть какой-то список, в котором упоминается все, что не может выбраться?

(Что-то более сжатое, чем само стандартное.)

4b9b3361

Ответ 1

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

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

Но обратите внимание, что язык позволяет деструкторам и swap метать. swap может бросить, в простейшем случае, если вы не перегружаете его, то std::swap выполняет построение копии, назначение и уничтожение, три операции, которые могут вызывать исключение (в зависимости от ваши типы).

Правила для деструкторов изменились в С++ 11, а это означает, что спецификация деструктора без исключений имеет неявную спецификацию noexcept, которая, в свою очередь, означает, что если она создала исключение, то время выполнения вызовет terminate, но вы может изменить спецификацию исключения на noexcept(false), а затем деструктор также может бросить.

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

Ответ 2

Так что это не отвечает на ваш вопрос - я искал немного из своего любопытства, но я считаю, что nothrow гарантированные функции/операторы в основном исходят из любых функций C-стиля, доступных на С++, а также нескольких функций, которые достаточно просты, чтобы дать такую ​​гарантию. В общем, не ожидалось, что для программ на С++ эта гарантия будет гарантирована (Когда следует использовать std:: nothrow?), и это даже не ясно, если такая гарантия покупает вам что-нибудь полезное в коде, который регулярно использует исключения. Я не смог найти исчерпывающий список всех функций С++, которые не являются функциями (пожалуйста, исправьте меня, если я пропустил стандарт, диктующий это), кроме списков подкачки, деструкторов и примитивных манипуляций. Также представляется довольно редкой функция, которая не полностью определена в библиотеке, чтобы потребовать от пользователя реализовать функцию nothrows.

Итак, возможно, чтобы добраться до корня вашего вопроса, вы должны в основном предполагать, что что-либо может выкинуть на С++ и принять его за упрощение, если вы найдете то, что абсолютно не может вызвать исключение. Написание кода, исключающего исключение, очень похоже на то, как писать бесплатный код - это сложнее, чем кажется, и, честно говоря, часто не стоит усилий. Кроме того, существует множество уровней между небезопасным кодом исключения и сильными функциями nothrow. Посмотрите на этот замечательный ответ о написании кода исключения для безопасности в качестве проверки для этих пунктов: Вы действительно написали безопасный код исключения?. Там больше информации о безопасности исключений на сайте ускорения http://www.boost.org/community/exception_safety.html.

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

В ответ на комментарии к исходному вопросу:

Люди иногда говорят, что конструкторы бросания исключений являются злыми при броске в контейнерах или вообще, и что необходимо всегда использовать двухэтапную инициализацию и проверку is_valid. Однако, если конструктор терпит неудачу, он часто нефиксируется или находится в однозначно плохом состоянии, иначе конструктор разрешил бы проблему в первую очередь. Проверка того, является ли объект действительным, так же сложно, как положить блок catch try вокруг кода инициализации для объектов, которые, как вы знаете, имеют приличную вероятность выбросить исключение. Итак, что правильно? Обычно то, что использовалось в остальной части базы кода, или ваши личные предпочтения. Я предпочитаю код, основанный на исключениях, поскольку он дает мне ощущение большей гибкости без тонны кода багажа для проверки каждого объекта на достоверность (другие могут не согласиться).

Где это оставляет вам оригинальный вопрос и расширения, указанные в комментариях? Ну, из предоставленных источников и моего собственного опыта, беспокоящегося о нечетких функциях в перспективе "Исключительной безопасности" С++, часто используется неправильный подход к обработке кода. Вместо этого имейте в виду, что функции, которые вы знаете, могут разумно генерировать исключение и обрабатывать эти случаи соответствующим образом. Обычно это связано с операциями ввода-вывода, когда у вас нет полного контроля над тем, что вызовет исключение. Если вы получаете исключение, которое вы никогда не ожидали или не считали возможным, то у вас есть ошибка в вашей логике (или ваши предположения о том, что функция использует), и вам нужно исправить исходный код для адаптации. Попытка сделать гарантии о нетривиальном (а иногда и даже когда-то) кодеке - это сказать, что сервер никогда не потерпит крах - он может быть очень стабильным, но вы, вероятно, не будете на 100% уверены.

Ответ 3

Если вы хотите, чтобы исчерпывающий ответ на этот вопрос перешел на http://exceptionsafecode.com/ и либо смотрел 85-минутное видео, которое охватывает только С++ 03 или трехчасовое (в двух частях) видео, которое охватывает как С++ 03, так и С++ 11.

При написании кода Exception-Safe мы предполагаем, что все функции throw, если только мы не знаем другого.

Короче говоря,

*) Фундаментальные типы (включая массивы и указатели на) могут быть назначены и из них и использоваться с операциями, которые не связаны с определенными пользователем операторами (математика с использованием только фундаментальных целых чисел и значений с плавающей запятой, например). Обратите внимание, что деление на ноль (или любое выражение, результат которого не определяется математически) - это поведение undefined и может или не может быть выбрано в зависимости от реализации.

*) Деструкторы: нет ничего принципиально неправильного с деструкторами, которые испускают исключения, и стандарт не запрещает их. Однако хорошие правила кодирования обычно запрещают их, потому что язык не поддерживает этот сценарий очень хорошо. (Например, если деструкторы объектов в контейнерах STL выбрасываются, поведение undefined.)

*) Использование swap() является важным методом обеспечения надежной гарантии исключения, но только если swap() не бросает вызов. В общем случае мы не можем предположить, что swap() не бросается, но в видео рассказывается о том, как создать не метательный своп для ваших пользовательских типов как в С++ 03, так и в С++ 11.

*) С++ 11 вводит семантику перемещения и операции перемещения. В С++ 11 swap() реализуется с использованием семантики перемещения, а ситуация с операциями перемещения аналогична ситуации с swap(). Мы не можем предположить, что операции перемещения не выбрасываются, но мы можем вообще создавать операции металирования без металирования для создаваемых пользователем типов, которые мы создаем (и они предоставляются для стандартных типов библиотек). Если мы предоставим неперебрасывающие операции перемещения в С++ 11, мы получим не-throwing swap() бесплатно, но мы можем реализовать любой способ свопа() для достижения производительности. Опять же, это подробно обложка в видео.

*) В С++ 11 вводится noexcept оператор и декоратор функций. (Спецификация "throw()" от Classic С++ теперь устарела.) Она также обеспечивает интроспекцию функций, чтобы код мог быть написан для обработки ситуаций по-разному в зависимости от того, существуют ли операции без металирования.

В дополнение к видео на веб-сайте exceptionsafecode.com есть библиография книг и статей об исключениях, которые необходимо обновить для С++ 11.