Когда и как использовать обработку исключений?

83

Я читаю об обработке исключений. Я получил некоторую информацию о том, что такое обработка исключений, но у меня есть несколько вопросов:

  1. Когда вызывать исключение?
  2. Можем ли мы использовать возвращаемое значение, чтобы указать ошибку, вместо того, чтобы генерировать исключение?
  3. Если я защищу все свои функции блоками try-catch, не снизится ли это производительность?
  4. Когда использовать обработку исключений?
  5. Я видел проект, в котором каждая функция в этом проекте содержала блок try-catch (т.е. код внутри всей функции окружен блоком try-catch). Это хорошая практика?
  6. В чем разница между try-catch и __try __except?
Умеша М.С.
источник
Вам нужно будет задать более конкретные вопросы, чем «Когда вызывать исключение». Вы бросаете исключение, когда происходит что-то исключительное.
Falmarri
Я писал об этом относительно PHP, но думаю, что почти все применимо к C ++. Прочтите сообщение в блоге .
ircmaxell

Ответы:

96

Вот довольно подробное руководство по исключениям, которое, я считаю, необходимо прочитать:

Исключения и обработка ошибок - C ++ FAQ или C ++ FAQ lite

Как правило, генерируйте исключение, когда ваша программа может идентифицировать внешнийпроблема, препятствующая исполнению. Если вы получаете данные с сервера и эти данные недействительны, вызовите исключение. Нет места на диске? Выбросить исключение. Космические лучи мешают вам запрашивать базу данных? Выбросить исключение. Но если вы получаете некорректные данные внутри вашей собственной программы - не создавайте исключения. Если ваша проблема связана с вашим собственным плохим кодом, лучше использовать ASSERT для защиты от нее. Обработка исключений необходима для выявления проблем, с которыми программа не может справиться, и для того, чтобы сообщить им о пользователе, поскольку пользователь может их решить. Но ошибки в вашей программе - это не то, с чем может справиться пользователь, поэтому сбой программы скажет не намного меньше, чем «Значение answer_to_life_and_universe_and_everything не равно 42! Этого не должно происходить !!!! 11».

Перехватить исключение, в котором вы можете сделать с ним что-нибудь полезное, например, отобразить окно сообщения. Я предпочитаю перехватывать исключение один раз внутри функции, которая каким-то образом обрабатывает ввод пользователя. Например, пользователь нажимает кнопку «Аннигилировать все хунамы», а внутри функции annihilateAllHunamsClicked () есть блок try ... catch, чтобы сказать «Я не могу». Хотя уничтожение hunamkind - сложная операция, требующая вызова десятков и десятков функций, есть только одна попытка ... ловушка, потому что для пользователя это атомарная операция - одно нажатие кнопки. Проверки исключений в каждой функции излишни и уродливы.

Кроме того, я не могу порекомендовать достаточно хорошо ознакомиться с RAII - то есть убедиться, что все инициализированные данные уничтожаются автоматически. И этого можно достичь, инициализировав как можно больше в стеке, а когда вам нужно что-то инициализировать в куче, используйте какой-то умный указатель. Все, что инициализировано в стеке, будет автоматически уничтожено при возникновении исключения. Если вы используете немые указатели в стиле C, вы рискуете утечкой памяти при возникновении исключения, потому что нет никого, кто мог бы очистить их после исключения (конечно, вы можете использовать указатели в стиле C как члены вашего класса, но убедитесь, что они позаботились в деструкторе).

Септаграмма
источник
Спасибо за ценный вклад. > «Обработка исключений необходима для выявления проблем, которые программа> не может обработать, и для того, чтобы сообщить им о пользователе, потому что пользователь может> справиться с ними». Вместо того, чтобы генерировать исключение, я могу вернуть значение ошибки, а в вызывающей функции я могу проверить наличие ошибки и отобразить сообщение, которое поможет пользователю справиться с этим.
Umesha MS
Еще раз взгляните на часто задаваемые вопросы, особенно на третий и четвертый фрагменты кода в этом разделе: parashift.com/c++-faq-lite/exceptions.html#faq-17.2 Исключения - это здорово, потому что они позволяют выявить ваши ошибки с любой глубины. стека вызовов ... Это как если коды ошибок - батисферы, исключения - подводные лодки :)
Септаграмма
2
@Septagram «Если ваша проблема возникла из-за вашего собственного плохого кода, лучше использовать ASSERT для защиты от нее»: утверждение не позволит вам продолжить выполнение задач, которые не зависят от ветки с ошибками (только потому, что ваша программа ошибка в одной функции не означает, что она не может делать другие полезные вещи). Это не позволит вам использовать собственные регистраторы. Он обойдет деструкторы, от которых вы выиграете, используя RAII (хотите, чтобы ваши файловые буферы были очищены до завершения программы? Лучше не пропускать эти деструкторы с помощью fclosethen!). Это не даст вам полной трассировки стека.
daemonspring 01
Что-то сказать о RAII и "тупых указателях". То, что вы используете «тупой указатель», не означает, что вы не следуете RAII. Помните, что только когда вы выделяете системные ресурсы, вам нужно убедиться, что они освобождены. Указатель - это просто переменная в стеке, содержащая адрес памяти. Для использования «тупого указателя» выделение и инициализация объекта не требуется. «Тупой указатель» может просто содержать адрес объекта, который размещен где-то еще, и его вполне можно использовать. (продолжение в следующем комментарии)
SeanRamey
У вас может быть объект Renderer, который хранит адрес объекта Display в указателе, чтобы рисовать объекты на Display, но вы не можете использовать интеллектуальный указатель, потому что интеллектуальный указатель уничтожит объект Display, когда вы уничтожите объект Renderer. , чего вы не хотите. В этом случае можно использовать либо «тупой указатель», либо ссылку.
SeanRamey
12

Исключения полезны во множестве обстоятельств.

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

Есть и другие случаи, когда ваш код уже сложен и передача информации об ошибках вверх по цепочке вызовов затруднена. Отчасти это связано с тем, что C и C ++ имеют сломанные модели структур данных: есть другие, более эффективные способы, но C ++ не поддерживает их (например, использование монад в Haskell). Это использование в основном я не мог беспокоиться о том, чтобы сделать это правильно, поэтому я выброшу исключение : это не правильный путь, но он практичный.

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

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

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

Иттрил
источник
15
Не могли бы вы добавить несколько ссылок / ссылок на «известно, что обработка исключений не работает» и «теоретики разработали более совершенные модели», пожалуйста?
Juraj Blaho
1
Важная вещь, о которой часто нужно знать при перехвате исключений, но о которой большинство исключений не предоставляют полезных данных, - это то, повлиял ли код, создавший исключение, на состояние системы. Имеет ли дело с этим какая-либо из моделей теоретиков?
supercat
@JurajBlaho Я безуспешно пытался найти ссылки / отсылки, о которых вы говорите. Можете ли вы отредактировать его ответ, чтобы добавить их в конце?
ForceMagic
Я согласен с примерами №1 и №3. Однако я думаю, что если код слишком сложен, поэтому вы думаете о выбросе исключения, возможно, вам потребуется некоторый рефакторинг. Assert также является хорошим способом (даже лучше) для проверки правильности работы кода, и на самом деле это дешевле, чем исключение. Я рекомендую 2 главы (Настойчивое программирование и Когда использовать исключения) из книги «Прагматичный программист» всем, кто интересуется этой темой.
ForceMagic
1
Ссылки не нужны. Используйте мозги. Вы можете создать исключение, которое не перехватывается. Это плохо. Хуже, чем goto, так как goto по крайней мере всегда куда-то идет. Статическая типизация не справляется со спецификациями исключений: не существует разумной системы, в которой полиморфные функции могли бы иметь спецификацию исключения, поскольку она зависит от конкретного экземпляра переменной типа. Обратите внимание, что они удалены из нового стандарта C ++. Новая модель называется «продолжением с разделителями»: en.wikipedia.org/wiki/Delimited_continuation
Иттрилл,
4

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

Я не согласен с этим аспектом принятого ответа . Утверждение не лучше, чем выброс исключения. Если исключения подходят только для ошибок времени выполнения (или «внешних проблем»), для чего std::logic_error?

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

Это не похоже на отсутствие предшествующего уровня техники. std::vector, чтобы назвать только одно, выдает исключение логической ошибки, а именно std::out_of_range. Если вы используете стандартную библиотеку и у вас нет обработчика верхнего уровня для перехвата стандартных исключений - хотя бы для вызова what () и exit (3) - тогда ваши программы подвергаются внезапному молчаливому завершению.

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

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

Джеймс К. Лоуден
источник
3

Лучшее чтение для этого

Об обработке исключений много говорилось за последние полтора десятилетия. Однако, несмотря на общее мнение о том, как правильно обрабатывать исключения, различия в использовании продолжают существовать. Неправильную обработку исключений легко обнаружить, легко избежать и это простой показатель качества кода (и разработчика). Я знаю, что абсолютные правила кажутся ограниченными или преувеличенными, но, как правило, вам не следует использовать try / catch

http://codebetter.com/karlseguin/2010/01/25/don-t-use-try-catch/

Сушан М
источник
3
Разве эта статья не пытается сказать, не используйте try/ catch {}?
Craig McQueen
2

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

2. Используйте блок try-catch только в тех случаях, когда это необходимо. Использование каждого блока try-catch добавляет дополнительную проверку условий, что, безусловно, снижает оптимизацию кода.

3. Я думаю, что _try_except - допустимое имя переменной ....

user550830
источник
3
FYI, __try и __except предназначены для обработки структурированных исключений Microsoft (SEH). msdn.microsoft.com/en-us/library/s58ftw19(v=vs.80).aspx
axw
0

Основное различие:

  1. один делает для вас обработку ошибок.
  2. один - вы делаете свое собственное.

    • Например, у вас может быть выражение 0 divide error. Использование try catch 1.поможет вам в случае возникновения ошибки. Или вам нужно if a==0 then..в2.

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

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

Совет: просто ведите себя, когда это просто и логично.

пиничи
источник
-3

Многие пуристы C / C ++ вообще не одобряют исключений. Основные критические замечания:

  1. Это медленно - конечно, не совсем "медленно". Однако по сравнению с чистым c / c ++ накладных расходов довольно мало.
  2. Он вносит ошибки - если вы не обрабатываете исключения должным образом, вы можете пропустить код очистки в функции, которая вызывает исключение.

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

скоростной самолет
источник
15
Бред какой то. Во-первых, мы говорим о C ++ - не существует "C / C ++", и, конечно, программисты, работающие только на C, будут испытывать дискомфорт с исключениями. «чистого C / C ++» не существует, а исключения являются частью «чистого C ++» - они включены в Стандарт. Вы можете написать ошибочный код обработки исключений, и вы можете написать ошибочный код, возвращающий ошибку, или ошибочный код состояния ошибки - неправильно характеризовать исключения как более подверженные ошибкам в целом. Извините, но это один из худших советов, которые я видел на SO
Тони Делрой
1
Отметьте меня, если хотите, но, исходя из опыта работы с C, я могу сказать вам, что я и многие многие другие вообще воздерживаются от использования исключений.
Speedplane
4
1. Это медленно только тогда, когда действительно сгенерировано исключение. А исключения называются исключениями, потому что они возникают только в исключительных случаях. Программа, генерирующая более 9000 исключений в секунду, делает это неправильно. 2. Если вы позаботитесь о том, чтобы не управлять памятью с помощью чистого new-delete, очистка будет выполнена за вас. Управление памятью в стиле C гораздо более подвержено ошибкам, чем исключения. 3. Проверка возвращаемого значения добавляет много дополнительных строк кода, что делает его менее читаемым и менее удобным в обслуживании.
Септаграмма
3
1) в зависимости от компилятора, что может не соответствовать действительности. И все исключения добавляют большое количество байтового кода, увеличивая размер исполняемого файла и замедляя работу. 2) все время используется чистое new-delete, не все может быть объектом стека. 3) проверка возвращаемых значений может потребовать большего количества строк кода, но, возможно, более удобна в обслуживании. Вы можете не согласиться, но это общепринятое разумное мнение, что исключения C ++ во многих случаях плохи. Взгляните на встроенный C ++ в качестве примера.
Speedplane
11 голосов против и 8 голосов за. Я думаю, очевидно, что, хотя это мнение не отражает общего консенсуса, есть большой контингент разработчиков, которые опасаются исключений по причинам, которые я описал.
Speedplane 03