Вы (Действительно) пишете исключению безопасный код? [закрытый]

Редактирование: А hacky способ сделать это должен был бы создать переменную, которая отслеживает фокус для каждого элемента, о котором Вы заботитесь. Так, если Вы заботитесь, что 'myInput' потерянный фокус, установите переменную на него на фокусе.

<script type="text/javascript">
   var lastFocusedElement;
</script>
<input id="myInput" onFocus="lastFocusedElement=this;" />

Исходный Ответ: можно передать 'это' функции.

<input id="myInput" onblur="function(this){
   var theId = this.id; // will be 'myInput'
}" />
308
задан 8 revs, 5 users 84% 13 January 2017 в 09:16
поделиться

13 ответов

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

Ответы на вопросы

Вы действительно пишете код, безопасный для исключений?

Конечно, пишу.

Это причина того, что Java потеряла свою привлекательность для меня как программиста на C ++ (отсутствие семантики RAII), но я отвлекся: это вопрос C ++.

На самом деле это необходимо, когда вам нужно работать с кодом STL или Boost. Например, потоки C ++ ( boost :: thread или std :: thread ) вызовут исключение для корректного завершения.

Вы уверены, что ваш последний "готовый к производству" код - в порядке исключения?

Можете ли вы быть уверены, что это такое?

Написание кода, безопасного для исключений, похоже на написание кода без ошибок.

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

Знаете ли вы и / или действительно используете альтернативы, которые работают?

Есть нет жизнеспособные альтернативы в C ++ (т.е. вам нужно будет вернуться к C и избегать библиотек C ++, а также внешних сюрпризов, таких как Windows SEH).

Написание кода, безопасного для исключений

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

Например, new может вызвать исключение, но присвоение встроенного (например, int или указателя) ) не подведет. Своп никогда не завершится ошибкой (никогда не пишите бросающий своп), a std :: list: : push_back может выбросить ...

Гарантия исключения

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

  1. none : Ваш код никогда не должен предлагать этого. Этот код приведет к утечке всего и выйдет из строя при самом первом сгенерированном исключении.
  2. basic : Это гарантия, которую вы должны как минимум предложить, то есть, если возникает исключение, утечки ресурсов не происходит, и все объекты по-прежнему целы
  3. сильный : обработка либо завершится успешно, либо вызовет исключение, но если она выдаст, то данные будут в том же состоянии, как если бы обработка вообще не началась (это дает возможность транзакций для C ++)
  4. nothrow / nofail : Обработка завершится успешно.

Пример кода

Следующий код выглядит как правильный C ++, но на самом деле предлагает гарантию «нет», и, следовательно, это неверно:

void doSomething(T & t)
{
   if(std::numeric_limits<int>::max() > t.integer)  // 1.   nothrow/nofail
      t.integer += 1 ;                              // 1'.  nothrow/nofail
   X * x = new X() ;                // 2. basic : can throw with new and X constructor
   t.list.push_back(x) ;            // 3. strong : can throw
   x->doSomethingThatCanThrow() ;   // 4. basic : can throw
}

Я пишу весь свой код с учетом такого рода анализа.

Самая низкая из предлагаемых гарантий является базовой, но затем порядок каждой инструкции делает всю функцию «none», потому что если 3. throws, x будет протекать.

Первое, что нужно сделать, это сделать функцию «базовой», то есть поместить x в умный указатель, пока он не станет безопасно принадлежать списку:

void doSomething(T & t)
{
   if(std::numeric_limits<int>::max() > t.integer)  // 1.   nothrow/nofail
      t.integer += 1 ;                              // 1'.  nothrow/nofail
   std::auto_ptr<X> x(new X()) ;    // 2.  basic : can throw with new and X constructor
   X * px = x.get() ;               // 2'. nothrow/nofail
   t.list.push_back(px) ;           // 3.  strong : can throw
   x.release() ;                    // 3'. nothrow/nofail
   px->doSomethingThatCanThrow() ;  // 4.  basic : can throw
}

Теперь наш код предлагает «базовую» гарантию. Ничего не протечет, и все объекты будут в исправном состоянии. Но мы могли бы предложить больше, то есть сильную гарантию. Именно здесь может стать дорогостоящим, и поэтому не весь код C ++ является сильным. Попробуем:

void doSomething(T & t)
{
   // we create "x"
   std::auto_ptr<X> x(new X()) ;    // 1. basic : can throw with new and X constructor
   X * px = x.get() ;               // 2. nothrow/nofail
   px->doSomethingThatCanThrow() ;  // 3. basic : can throw

   // we copy the original container to avoid changing it
   T t2(t) ;                        // 4. strong : can throw with T copy-constructor

   // we put "x" in the copied container
   t2.list.push_back(px) ;          // 5. strong : can throw
   x.release() ;                    // 6. nothrow/nofail
   if(std::numeric_limits<int>::max() > t2.integer)  // 7.   nothrow/nofail
      t2.integer += 1 ;                              // 7'.  nothrow/nofail

   // we swap both containers
   t.swap(t2) ;                     // 8. nothrow/nofail
}

Мы изменили порядок операций, сначала создайте и установите X на правильное значение. Если какая-либо операция завершается неудачно, то t не изменяется, поэтому операции с 1 по 3 могут считаться «сильными»: если что-то выдает, t не изменяется, а X не будет протекать, потому что он принадлежит интеллектуальному указателю.

Затем мы создаем копию t2 из t и работаем с этой копией с операций с 4 по 7. Если что-то бросает, t2 изменяется, но тогда t по-прежнему остается исходным. Мы по-прежнему предлагаем сильную гарантию.

Затем мы меняем местами t и t2 . Операции подкачки не должны выполняться в C ++, так что будем надеяться, что своп, который вы написали для T , не является свопом (если это не так, перепишите его так, чтобы он не вытеснялся).

Итак, если мы дойдем до конца функции, все будет выполнено успешно (тип возврата не требуется) и t имеет исключенное значение. Если это не удается, то t все еще имеет свое первоначальное значение.

Теперь предложение сильной гарантии может быть довольно дорогостоящим, поэтому не стремитесь предлагать сильную гарантию для всего вашего кода, но если вы может сделать это без затрат (а встраивание C ++ и другая оптимизация могут сделать весь приведенный выше код без затрат), а затем сделайте это. Пользователь функции поблагодарит вас за это.

Заключение

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

Конечно, компилятор C ++ не будет выполнять резервное копирование гарантия (в моем коде, Я предлагаю гарантию в виде тега @warning doxygen), что отчасти печально, но оно не должно останавливать вас от попыток написать безопасный для исключений код.

Нормальный сбой или ошибка.

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

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

Исключения относятся к исключительной ошибке обработки, а не к ошибкам кода.

Последние слова

Теперь вопрос: «Стоит ли это того?».

Of конечно, это так. Иметь "

  • [я] Своп никогда не потерпит неудачу (даже не пишите бросающий своп)
  • [nobar] Это хорошая рекомендация для специально написанных функций swap () . Однако следует отметить, что std :: swap () может завершиться ошибкой в ​​зависимости от операций, которые он использует внутри

, значение по умолчанию std :: swap будет делать копии и назначения , который для некоторых объектов может бросать. Таким образом, своп по умолчанию может быть использован либо для ваших классов, либо даже для классов STL. Что касается стандарта C ++, операция подкачки для vector , deque и list не будет вызывать, тогда как для map , если функтор сравнения может вызывать копирование (см. Язык программирования C ++, специальный выпуск, приложение E, E.4.3.Swap ).

Рассмотрение реализации вектора в Visual C ++ 2008 поменять местами, своп векторов не сработает, если два вектора имеют один и тот же распределитель (т. е. в нормальном случае), но будут делать копии, если у них разные распределители. И поэтому я предполагаю, что в последнем случае он мог бы бросить.

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

Edit 2011-11-06: Интересная статья

Дэйв Абрахамс , который дал нам базовые / сильные / ненадежные гарантии , описал в статье свой опыт о обеспечение безопасности исключений STL:

http://www.boost.org/community/exception_safety.html

Взгляните на 7-й пункт (Автоматическое тестирование на безопасность исключений), где он полагается на автоматическое модульное тестирование, чтобы сделать уверен, что каждый случай проверен. Думаю, эта часть является отличным ответом на вопрос автора « Можете ли вы быть уверены, что это так? ».

Редактировать 2013-05-31: Комментарий от dionadar

t.integer + = 1; не дает гарантии того, что переполнение не произойдет, НЕ безопасно для исключений и фактически может технически вызвать UB! (Переполнение со знаком - UB: C ++ 11 5/4 «Если во время вычисления выражения результат математически не определен или не находится в диапазоне представимых значений для его типа, поведение не определено».) Обратите внимание, что без знака integer не переполняются, но выполняют свои вычисления в классе эквивалентности по модулю 2 ^ # битов.

Dionadar имеет в виду следующую строку, которая действительно имеет неопределенное поведение.

   t.integer += 1 ;                 // 1. nothrow/nofail

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

Я исправил код с учетом комментария Дионадара.

514
ответ дан 23 November 2019 в 01:17
поделиться

Некоторые из нас используют исключения более 20 лет. Они есть, например, в PL / I. Предпосылка о том, что это новая и опасная технология, кажется мне сомнительной.

18
ответ дан 23 November 2019 в 01:17
поделиться

Написание безопасного в отношении исключений кода на C ++ - это не столько использование большого количества блоков try {} catch {}. Речь идет о документировании того, какие гарантии предоставляет ваш код.

Я рекомендую прочитать серию статей Херба Саттера Гуру недели , в частности, выпуски 59, 60 и 61.

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

  • Базовый: когда ваш код генерирует исключение, ваш код не пропускает ресурсы, и объекты остаются разрушаемыми.
  • Сильно: когда ваш код генерирует исключение,
31
ответ дан 23 November 2019 в 01:17
поделиться
  • Вы действительно пишете код, безопасный для исключений?

Что ж, я определенно собираюсь.

  • Вы уверены, что ваш последний "готовый к производству" код безопасен в отношении исключений?

I ' Я уверен, что мои 24/7 серверы, построенные с использованием исключений, работают 24/7 и не пропускают память.

  • Можете ли вы даже быть уверены, что это так?

Очень трудно быть уверенным, что какой-либо код правильный. Обычно можно исходить только из результатов

  • Знаете ли вы и / или действительно используете альтернативные варианты, которые работают?

Нет. Использование исключений проще и проще, чем любые альтернативы, которые я использовал в программировании за последние 30 лет.

8
ответ дан 23 November 2019 в 01:17
поделиться

Мне очень нравится работать с Eclipse и Java (впервые в Java), потому что это вызывает ошибки в редакторе, если вам не хватает обработчика EH. Из-за этого НАМНОГО труднее забыть обработать исключение ...

Кроме того, с инструментами IDE он автоматически добавляет блок try / catch или другой блок catch.

2
ответ дан 23 November 2019 в 01:17
поделиться

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

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

9
ответ дан 23 November 2019 в 01:17
поделиться

Не говоря уже о путанице между исключениями SEH и C ++, вы должны знать, что исключения могут быть выброшены в любое время, и с учетом этого при написании кода. Потребность в безопасности исключений во многом является движущей силой использования RAII, интеллектуальных указателей и других современных методов C ++.

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

5
ответ дан 23 November 2019 в 01:17
поделиться

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

Это означает, что я стараюсь следить за , которые могут выдавать строки. Не все могут, и очень важно помнить об этом. Ключ действительно состоит в том, чтобы продумать и спроектировать свой код так, чтобы он удовлетворял гарантиям исключений, определенным в стандарте.

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

2
ответ дан 23 November 2019 в 01:17
поделиться

Некоторые из нас предпочитают такие языки, как Java, которые заставляют нас объявлять все исключения, создаваемые методами, вместо того, чтобы делать их невидимыми, как в C ++ и C #.

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

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

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

2
ответ дан 23 November 2019 в 01:17
поделиться
  • Вы действительно пишете исключение безопасный код? [Нет такого понятия. Исключения - это бумажный щит от ошибок, если у вас нет управляемой среды. Это относится к первым трем вопросам.]

  • Знаете ли вы и / или действительно ли используете альтернативы, которые работают? [Альтернатива чему? Проблема здесь в том, что люди не отделяют реальные ошибки от нормальной работы программы. Если это нормальная работа программы (т.е. файл не найден), это не совсем обработка ошибок. Если это настоящая ошибка, нет способа «обработать» ее, иначе это не настоящая ошибка. Ваша цель здесь - выяснить, что пошло не так, и либо остановить электронную таблицу и записать ошибку, перезапустить драйвер вашего тостера, либо просто молиться, чтобы истребитель мог продолжать полет, даже если его программное обеспечение неисправно, и надеяться на лучшее.]

2
ответ дан 23 November 2019 в 01:17
поделиться

Прежде всего (как заявил Нил) SEH - это структурированная обработка исключений Microsoft. Он похож на обработку исключений в C ++, но не идентичен ей. Фактически, вы должны включить обработку исключений C ++ , если хотите, в Visual Studio - поведение по умолчанию не гарантирует, что локальные объекты будут уничтожены во всех случаях! В любом случае обработка исключений на самом деле не сложнее , а просто другое .

Теперь к вашим актуальным вопросам.

Вы действительно пишете безопасный код исключений?

Да. Я стремлюсь к исключению безопасного кода во всех случаях. Я проповедую использование методов RAII для ограниченного доступа к ресурсам (например, boost :: shared_ptr для памяти, boost :: lock_guard для блокировки). В основном, последовательное использование методов RAII и защиты области значительно упростит написание кода, безопасного для исключений. Уловка состоит в том, чтобы узнать, что существует и как это применять.

Вы уверены, что ваш последний "готовый к производству" код безопасен в отношении исключений?

Нет. Это так же безопасно, как и есть. Могу сказать, что я не видел сбоев процесса из-за исключения за несколько лет работы 24/7. Я не жду идеального кода, просто хорошо написанного кода. В дополнение к обеспечению безопасности исключений, описанные выше методы гарантируют корректность способом, который практически невозможно достичь с помощью блоков try / catch . Если вы ловите все, что находится в области верхнего элемента управления (поток, процесс и т. Д.), тогда вы можете быть уверены, что продолжите работать, несмотря на исключения ( большую часть времени ). Те же методы также помогут вам продолжать правильно выполнять перед лицом исключений без блоков try / catch везде .

Можете ли вы быть уверены, что это так?

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

Знаете ли вы и / или действительно ли используете альтернативы, которые работают?

За эти годы я пробовал несколько вариантов, таких как состояния кодирования в верхних битах (аля HRESULT s ) или этот ужасный хак setjmp () ... longjmp () . Оба они на практике не работают, хотя и совершенно по-разному.


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

  • Вы хотите видеть try / catch только тогда, когда можете что-то сделать с конкретным исключением
  • Вы почти никогда не хотите см. необработанный новый или delete в коде
  • Eschew std :: sprintf , snprintf и массивы в целом - используйте std :: ostringstream для форматирования и замены массивов на std :: vector и std :: string
  • Если есть сомнения, поищите функциональные возможности в Boost или STL перед тем, как запускать собственную

. Я могу только порекомендовать вам научиться правильно использовать исключения и забыть о кодах результатов, если вы планируете писать на C ++. Если вы хотите избежать исключений, вы можете подумать о написании на другом языке, который либо не имеет их , либо делает их безопасными . Если вы действительно хотите узнать, как в полной мере использовать C ++, прочтите несколько книг от Херба Саттера , Николая Джозаттиса и Скотта Мейерса .

17
ответ дан 23 November 2019 в 01:17
поделиться

Многие (я бы даже сказал, большинство) людей так и поступают.

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

Вам нужно активно делать ошибки в обработчиках, чтобы получить что-то небезопасное, и только catch (...) {} будет сравниваться с игнорированием кода ошибки.

0
ответ дан 23 November 2019 в 01:17
поделиться

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

3
ответ дан 23 November 2019 в 01:17
поделиться
Другие вопросы по тегам:

Похожие вопросы: