Обработка исключений (EH), кажется, является текущим стандартом, и, ища в Интернете, я не могу найти какие-либо новые идеи или методы, которые пытаются улучшить или заменить его (ну, некоторые вариации существуют, но ничего нового).
Хотя большинство людей, кажется, игнорируют это или просто принимают, EH имеет некоторые огромные недостатки: исключения невидимы для кода, и это создает много-много возможных точек выхода. Джоэл о программном обеспечении написал статью об этом . Сравнение с goto
идеальной идеей заставило меня снова задуматься об EH.
Я стараюсь избегать EH и просто использовать возвращаемые значения, обратные вызовы или что-то еще, соответствующее цели Но когда вам нужно написать надежный код, вы просто не можете игнорировать EH в наши дни : он начинается с new
, который может вызвать исключение, а не просто возвращать 0 (как в старые времена). Это делает любую строку кода C ++ уязвимой для исключения. А затем еще больше мест в основополагающем коде C ++ генерируют исключения ... std lib делает это и так далее.
Это похоже на хождение по шаткой местности ... Итак, теперь мы вынуждены заботиться об исключениях!
Но это тяжело, это действительно тяжело. Вы должны научиться писать безопасный код исключений, и даже если у вас есть некоторый опыт работы с ним, все равно потребуется перепроверить любую отдельную строку кода, чтобы быть безопасным! Или вы начинаете помещать блоки try / catch везде, что загромождает код, пока он не достигнет состояния нечитаемости.
EH заменил старый чистый детерминистический подход (возвращаемые значения ..), который имел всего несколько, но понятных и легко решаемых недостатков, подходом, который создает множество возможных точек выхода в вашем коде, и если вы начинаете писать код, который перехватывает исключения (то, что вы вынуждены делать это в какой-то момент), затем он даже создает множество путей через ваш код (код в блоках перехвата, подумайте о серверной программе, где вам нужны средства ведения журналов, отличные от std :: cerr ..). У EH есть преимущества, но это не главное.
Мои актуальные вопросы:
- Вы действительно пишете код безопасности исключений?
- Вы уверены, что ваш последний «готовый к работе» код безопасен для исключений?
- Можете ли вы быть уверены, что это так?
- Вы знаете и / или действительно используете альтернативы, которые работают?
источник
Ответы:
В вашем вопросе утверждается, что «написание исключительного кода очень сложно». Сначала я отвечу на ваши вопросы, а затем отвечу на скрытый за ними вопрос.
Отвечая на вопросы
Конечно, я делаю.
По этой причине Java потеряла свою привлекательность для меня как программиста C ++ (отсутствие семантики RAII), но я отвлекаюсь: это вопрос C ++.
Фактически это необходимо, когда вам нужно работать с кодом STL или Boost. Например, потоки C ++ (
boost::thread
илиstd::thread
) сгенерируют исключение для корректного выхода.Написание кода, исключающего исключения, похоже на написание кода без ошибок.
Вы не можете быть на 100% уверены, что ваш код безопасен от исключений. Но затем вы стремитесь к этому, используя известные шаблоны и избегая известных анти-шаблонов.
В C ++ нет жизнеспособных альтернатив (т.е. вам нужно вернуться к C и избежать библиотек C ++, а также внешних неожиданностей, таких как Windows SEH).
Написание исключительного безопасного кода
Чтобы написать код безопасности исключений, вы должны сначала знать, каков уровень безопасности исключений для каждой написанной вами инструкции.
Например, a
new
может выдать исключение, но назначение встроенного (например, int или указателя) не завершится неудачей. Своп никогда не потерпит неудачу (никогда не пишите своп броска),std::list::push_back
может бросить ...Гарантия исключения
Первое, что нужно понять, это то, что вы должны быть в состоянии оценить гарантию исключения, предлагаемую всеми вашими функциями:
Пример кода
Следующий код кажется правильным C ++, но на самом деле предлагает гарантию «none», и, следовательно, он не корректен:
Я пишу весь свой код с учетом такого анализа.
Самая низкая предлагаемая гарантия - базовая, но при этом порядок каждой инструкции делает всю функцию «нулевой», потому что, если 3. throws, x утечет.
Первое, что нужно сделать, это сделать функцию «базовой», то есть поместить умный указатель в x, пока он не будет безопасно принадлежать списку:
Теперь наш код предлагает «базовую» гарантию. Ничего не протечет, и все объекты будут в правильном состоянии. Но мы могли бы предложить больше, то есть сильную гарантию. Вот где это может стать дорогостоящим, и поэтому не весь код C ++ является сильным. Давай попробуем:
Мы переупорядочили операции, сначала создав и установив
X
их правильное значение. Если какая-либо операция завершается неудачно, тоt
она не изменяется, поэтому операции с 1 по 3 можно считать «сильной»: если что-то выбрасывается,t
не изменяется иX
не будет течь, потому что принадлежит интеллектуальному указателю.Затем мы создаем копию
t2
изt
, и работать на этой копии от операции 4 до 7. Если что - то бросает,t2
модифицируется, но затем, по-t
прежнему является оригинальным. Мы все еще предлагаем сильную гарантию.Затем мы переставляем
t
иt2
. Операции подкачки должны быть nothrow в C ++, поэтому будем надеяться, чтоT
подкачка, для которой вы написали, - nothrow (если это не так, переписайте ее, чтобы она не стала выше).Таким образом, если мы достигнем конца функции, все выполнится успешно (нет необходимости в возвращаемом типе) и будет
t
иметь свое исключенное значение. Если это не удастся, значитt
, все еще имеет свое первоначальное значение.Теперь предоставление надежной гарантии может быть довольно дорогостоящим, поэтому не пытайтесь предлагать надежную гарантию всему своему коду, но если вы можете сделать это без затрат (а встраивание C ++ и другая оптимизация могут сделать весь код выше бесплатным) , затем сделать его. Пользователь функции поблагодарит вас за это.
Вывод
Требуется некоторая привычка для написания безопасного кода. Вам нужно будет оценить гарантию, предлагаемую каждой инструкцией, которую вы будете использовать, а затем вам нужно будет оценить гарантию, предлагаемую списком инструкций.
Конечно, компилятор C ++ не будет резервировать гарантию (в моем коде я предлагаю гарантию в виде тега @warning doxygen), что довольно печально, но не должно мешать вам пытаться писать код, безопасный для исключений.
Нормальная ошибка против ошибки
Как программист может гарантировать, что безотказная функция всегда будет успешной? В конце концов, функция может иметь ошибку.
Это верно. Предполагается, что исключения гарантируются кодом без ошибок. Но тогда, на любом языке, вызов функции предполагает, что функция не содержит ошибок. Никакой вменяемый код не защищает себя от возможной ошибки. Напишите код как можно лучше, а затем предложите гарантию, предполагая, что он не содержит ошибок. И если есть ошибка, исправьте ее.
Исключения - исключительная ошибка обработки, а не ошибки кода.
Последние слова
Теперь вопрос: «Стоит ли это того?».
Конечно, это является. Наличие функции «nothrow / no-fail», зная, что функция не потерпит неудачу, является большим благом. То же самое можно сказать и о «сильной» функции, которая позволяет писать код с семантикой транзакций, такой как базы данных, с функциями фиксации / отката, когда коммит является нормальным выполнением кода, а исключения - откатом.
Тогда «базовая» - это минимальная гарантия, которую вы должны предложить. C ++ - очень сильный язык, с его областями действия, позволяющими избежать любых утечек ресурсов (то, что сборщику мусора будет трудно предложить для базы данных, соединения или файловых дескрипторов).
Итак, насколько я понимаю, это является стоит.
Редактировать 2010-01-29: О безбросовом свопе
nobar сделал комментарий, который, на мой взгляд, весьма актуален, поскольку он является частью «как вы пишете безопасный код исключения»:
swap()
функций. Следует отметить, однако, чтоstd::swap()
может произойти сбой на основе операций, которые он использует внутрипо умолчанию
std::swap
будут создаваться копии и назначения, которые для некоторых объектов могут генерировать. Таким образом, подкачка по умолчанию может сгенерировать, либо для ваших классов, либо даже для классов STL. Что касается стандарта C ++, то операция swap дляvector
,deque
иlist
не будет выбрасываться, тогда как это возможно,map
если функтор сравнения может сгенерировать конструкцию копирования (см. Язык программирования C ++, Специальный выпуск, приложение E, E.4.3 Своп )Если посмотреть на реализацию подкачки вектора в Visual C ++ 2008, то подкачка вектора не сработает, если два вектора имеют одинаковый распределитель (т. Е. Обычный случай), но будет делать копии, если они имеют разные распределители. И, таким образом, я предполагаю, что это может бросить в этом последнем случае.
Таким образом, исходный текст остается в силе: никогда не пишите своп-бросок, но необходимо запомнить комментарий Нобара: убедитесь, что объекты, которые вы меняете, имеют не-бросающий своп.
Редактировать 2011-11-06: Интересная статья
Дейв Абрахамс , который дал нам основные / сильные / nothrow гарантии , в своей статье описал свой опыт по обеспечению безопасности исключения STL:
http://www.boost.org/community/exception_safety.html
Посмотрите на 7-й пункт (Автоматизированное тестирование для исключительной безопасности), где он полагается на автоматическое модульное тестирование, чтобы убедиться, что каждый случай проверен. Думаю, эта часть является отличным ответом на вопрос автора « Можете ли вы быть уверены, что это так? ».
Изменить 2013-05-31: Комментарий от дионадар
Dionadar ссылается на следующую строку, которая действительно имеет неопределенное поведение.
Решение здесь состоит в том, чтобы проверить, является ли целое число уже в его максимальном значении (использование
std::numeric_limits<T>::max()
) перед выполнением сложения.Моя ошибка будет в разделе «Нормальная ошибка против ошибки», то есть ошибка. Это не лишает законной силы аргументацию и не означает, что безопасный код исключений бесполезен, потому что его невозможно достичь. Вы не можете защитить себя от выключения компьютера, ошибок компиляции, даже ваших ошибок или других ошибок. Вы не можете достичь совершенства, но вы можете попытаться приблизиться как можно ближе.
Я исправил код с учетом комментария Дионадара.
источник
"finally" does exactly what you were trying to achieve
Конечно, это так. И поскольку с помощью него легко создавать хрупкий кодfinally
, в Java 7 было наконец введено понятие «попробуй с ресурсом» (то есть через 10 лет после C #using
и 3 десятилетия после деструкторов C ++). Именно эту хрупкость я критикую. Что касаетсяJust because [finally] doesn't match your taste (RAII doesn't match mine, [...]) doesn't mean it's "failing"
: в этом индустрия не согласна с вашим вкусом, так как языки сборки мусора, как правило, добавляют операторы, основанные на RAII (C #using
и Javatry
).RAII doesn't match mine, since it needs a new struct every single darn time, which is tedious sometimes
Нет, это не так. Вы можете использовать умные указатели или служебные классы для «защиты» ресурсов.Написание безопасного для исключений кода на C ++ - это не столько использование множества блоков try {} catch {}. Речь идет о документировании того, какие гарантии предоставляет ваш код.
Я рекомендую прочитать серию « Гуру недели» Херба Саттера , в частности, серии 59, 60 и 61.
Подводя итог, можно выделить три уровня безопасности исключений:
Лично я обнаружил эти статьи довольно поздно, поэтому большая часть моего кода на C ++ определенно не безопасна для исключений.
источник
Некоторые из нас используют исключения уже более 20 лет. Например, они есть у PL / I. Предпосылка, что они являются новой и опасной технологией, кажется мне сомнительной.
источник
Прежде всего (как сказал Нил), SEH - это структурированная обработка исключений Microsoft. Это похоже, но не идентично обработке исключений в C ++. Фактически, вы должны включить обработку исключений C ++, если вы хотите это в Visual Studio - поведение по умолчанию не гарантирует, что локальные объекты будут уничтожены во всех случаях! В любом случае, обработка исключений на самом деле не сложнее, она просто другая .
Теперь для ваших актуальных вопросов.
Да. Я стремлюсь к исключению безопасного кода во всех случаях. Я проповедую, используя методы RAII для ограниченного доступа к ресурсам (например,
boost::shared_ptr
для памяти,boost::lock_guard
для блокировки). В целом, последовательное использование RAII и методов защиты контекста значительно облегчит написание кода, безопасного для исключений. Хитрость заключается в том, чтобы узнать, что существует и как его применять.Нет. Это так же безопасно, как и. Могу сказать, что я не видел сбоя процесса из-за исключения в течение нескольких лет круглосуточной работы. Я не ожидаю идеального кода, просто хорошо написанный код. В дополнение к обеспечению безопасности исключений, описанные выше методы гарантируют правильность способом, которого практически невозможно достичь с помощью
try
/catch
блоков. Если вы перехватываете все в своей верхней области управления (поток, процесс и т. Д.), То вы можете быть уверены, что продолжите работать в условиях исключений ( большую часть времени ). Те же самые приемы также помогут вам продолжать работать правильно, несмотря на исключения без блоковtry
/catch
везде .Да. Вы можете быть уверены в тщательном аудите кода, но никто этого не делает? Регулярные обзоры кода и осторожные разработчики имеют большое значение для достижения этой цели.
За несколько лет я попробовал несколько вариантов, таких как состояния кодирования в старших битах (ala
HRESULT
s ) или этот ужасныйsetjmp() ... longjmp()
хак. Оба из них ломаются на практике, хотя совершенно по-разному.В конце концов, если вы привыкнете применять несколько методов и тщательно продумывать, где вы можете что-то сделать в ответ на исключение, вы получите очень читаемый код, который безопасен для исключений. Вы можете суммировать это, следуя этим правилам:
try
/catch
когда вы можете сделать что-то с конкретным исключениемnew
илиdelete
в кодеstd::sprintf
,snprintf
и массивы в целом - используйтеstd::ostringstream
для форматирования и замены массивов наstd::vector
иstd::string
Я могу только порекомендовать вам научиться правильно использовать исключения и забыть о кодах результата, если вы планируете писать на C ++. Если вы хотите избежать исключений, вы можете рассмотреть возможность написания на другом языке, который либо не имеет их, либо делает их безопасными . Если вы действительно хотите научиться полностью использовать C ++, прочитайте несколько книг Херба Саттера , Николая Йосуттиса и Скотта Мейерса .
источник
new
илиdelete
в коде»: под необработанным, я полагаю, вы имеете в виду вне конструктора или деструктора.delete
никогда не должен использоваться вне реализацииtr1::shared_ptr
и тому подобное.new
может быть использован при условии, что его использование что-то вродеtr1::shared_ptr<X> ptr(new X(arg, arg));
. Важной частью является то, что результатnew
непосредственно переходит в управляемый указатель. Страница наboost::shared_ptr
Best Practices описывает лучшее.Невозможно написать безопасный для исключения код в предположении, что «любая строка может выдать». Разработка кода, исключающего исключительные ситуации, основывается на определенных контрактах / гарантиях, которые вы должны ожидать, соблюдать, соблюдать и реализовывать в своем коде. Это абсолютно необходимо иметь код, который гарантированно никогда не выкинет. Существуют и другие виды исключительных гарантий.
Другими словами, создание кода, исключающего исключения, в значительной степени является вопросом проектирования программы, а не просто вопросом простого кодирования .
источник
Ну, я определенно собираюсь.
Я уверен, что мои 24/7 серверы, созданные с использованием исключений, работают круглосуточно и не пропускают память.
Очень трудно быть уверенным, что любой код верен. Как правило, можно идти только по результатам
Нет. Использование исключений проще и проще, чем любая из альтернатив, которые я использовал в программировании за последние 30 лет.
источник
Оставляя в стороне путаницу между исключениями SEH и C ++, вы должны знать, что исключения могут быть созданы в любое время, и писать свой код с учетом этого. Потребность в исключительной безопасности во многом определяет использование RAII, интеллектуальных указателей и других современных методов C ++.
Если вы следуете хорошо зарекомендовавшим себя шаблонам, написание безопасного для исключений кода не представляет особой сложности, и на самом деле это проще, чем написание кода, который правильно обрабатывает возврат ошибок во всех случаях.
источник
Эх хорошо, в общем. Но реализация C ++ не очень дружелюбна, так как трудно сказать, насколько хорош ваш охват исключений. Java, например, делает это легко, компилятор будет склонен к сбою, если вы не обработаете возможные исключения.
источник
noexcept
.Мне действительно нравится работать с Eclipse и Java (хотя и новичок в Java), потому что он выдает ошибки в редакторе, если вам не хватает обработчика EH. Это делает вещи намного сложнее забыть, чтобы обработать исключение ...
Кроме того, с помощью инструментов IDE он автоматически добавляет блок try / catch или другой блок catch.
источник
Некоторые из нас предпочитают такие языки, как Java, которые заставляют нас объявлять все исключения, создаваемые методами, вместо того, чтобы делать их невидимыми, как в C ++ и C #.
Если все сделано правильно, исключения превосходят коды возврата ошибок, если по какой-либо другой причине, кроме случаев, когда вам не нужно распространять ошибки вверх по цепочке вызовов вручную.
При этом низкоуровневое программирование API-библиотеки, вероятно, должно избегать обработки исключений и придерживаться кодов возврата ошибок.
По моему опыту, трудно написать чистый код обработки исключений на C ++. Я использую
new(nothrow)
много.источник
new(std::nothrow)
не достаточно.Я стараюсь изо всех сил, чтобы написать исключительный код, да.
Это означает , что я позаботьтесь , чтобы следить за который строки могут быть выброшены. Не каждый может, и это крайне важно помнить об этом. Главное - подумать и спроектировать свой код так, чтобы он удовлетворял гарантиям исключения, определенным в стандарте.
Можно ли написать эту операцию, чтобы обеспечить гарантию исключений? Должен ли я согласиться на основной? Какие строки могут выдавать исключения, и как я могу гарантировать, что если они это сделают, они не повредят объект?
источник
Вы действительно пишете код безопасности исключений? [Нет такого понятия. Исключения - это защита от ошибок, если у вас нет управляемой среды. Это относится к первым трем вопросам.]
Вы знаете и / или действительно используете альтернативы, которые работают? [Альтернатива чему? Проблема в том, что люди не отделяют реальные ошибки от нормальной работы программы. Если это нормальная работа программы (т.е. файл не найден), это не совсем обработка ошибок. Если это фактическая ошибка, нет способа «обработать» ее, или это не фактическая ошибка. Ваша цель здесь - выяснить, что пошло не так, и либо остановить электронную таблицу и записать ошибку, перезапустить драйвер на тостер, либо просто помолиться о том, чтобы реактивный истребитель продолжал летать, даже если его программное обеспечение глючит, и надеяться на лучшее.]
источник
Многие (я бы даже сказал, большинство) делают.
Что действительно важно в отношении исключений, так это то, что если вы не пишете какой-либо код обработки - результат будет совершенно безопасным и хорошо себя ведет. Слишком жаждущий паники, но в безопасности.
Вам нужно активно делать ошибки в обработчиках, чтобы получить что-то небезопасное, и только catch (...) {} будет сравниваться с игнорированием кода ошибки.
источник
f = new foo(); f->doSomething(); delete f;
если метод doSomething выдает исключение, у вас есть утечка памяти.