Краткий ответ: прочитайте Обработка чувствительных ошибок 1 , Обработка понятных ошибок 2 и Обработка чувствительных ошибок 3 от Niklas Frykholm. На самом деле, прочитайте все статьи в этом блоге, пока вы на нем. Не скажу, что я согласен со всем, но в основном это золото.
Не используйте исключения. Есть множество причин. Я перечислю основные.
Они действительно могут быть медленнее, хотя на новых компиляторах это сведено к минимуму. Некоторые компиляторы поддерживают «исключения с нулевыми издержками» для путей кода, которые на самом деле не вызывают исключение (хотя это немного неправда, поскольку есть еще дополнительные данные, которые требуются для обработки исключений, что приводит к увеличению размера исполняемого файла / DLL). Однако конечный результат заключается в том, что да, использование исключений происходит медленнее, и в любых критичных к производительности путях кода их следует избегать. В зависимости от вашего компилятора их включение может привести к дополнительным расходам. Они всегда всегда увеличивают размер кода, довольно значительно во многих случаях, что может серьезно повлиять на производительность на современном оборудовании.
Исключения делают код гораздо более хрупким. Есть печально известная графика (которую я, к сожалению, не могу найти прямо сейчас), которая в основном просто показывает на диаграмме сложность написания кода, исключающего исключение, по сравнению с кодом, небезопасным для исключения, а первый представляет собой значительно большую полосу. Просто есть много маленьких ошибок с исключениями и множество способов написания кода, который выглядит безопасным, но на самом деле это не так. Даже весь комитет по C ++ 11 отошел от этого и забыл добавить важные вспомогательные функции для правильного использования std :: unique_ptr, и даже с этими вспомогательными функциями требуется больше набирать их, чем нет, и большинство программистов победили ' даже не понимаю, что не так, если они этого не делают.
В частности, для игровой индустрии некоторые компиляторы / среды выполнения, предоставляемые поставщиками консолей, не поддерживают полностью исключения или даже не поддерживают их вообще. Если в вашем коде используются исключения, возможно, вам все еще придется переписать части кода для переноса его на новые платформы. (Не уверен, что это изменилось за 7 лет, прошедших с момента выхода указанных консолей; мы просто не используем исключения, даже отключаем их в настройках компилятора, поэтому я не знаю, кто-нибудь, с кем я общался, даже проверял недавно.)
Общая линия мышления довольно ясна: используйте исключения для исключительных обстоятельств. Используйте их, когда ваша программа перейдет к «Я понятия не имею, что делать, возможно, надеюсь, что кто-то другой сделает, поэтому я скину исключение и посмотрю, что произойдет». Используйте их, когда никакой другой вариант не имеет смысла. Используйте их, когда вам все равно, если вы случайно утечете немного памяти или не сможете очистить ресурс, потому что испортили использование правильных умных ручек. Во всех остальных случаях не используйте их.
Что касается кода, подобного вашему примеру, у вас есть несколько других способов решить проблему. Один из наиболее надежных - хотя и не обязательно самый идеальный в вашем простом примере - склоняется к типам монадических ошибок. То есть createText () может возвращать пользовательский тип дескриптора, а не целое число. Этот тип дескриптора имеет средства доступа для обновления или управления текстом. Если дескриптор переводится в состояние ошибки (из-за сбоя createText ()), дальнейшие вызовы дескриптора просто молча завершаются неудачей. Вы также можете запросить дескриптор, чтобы узнать, не ошиблась ли она, и если да, то какая была последняя ошибка. Этот подход имеет больше накладных расходов, чем другие варианты, но он довольно солидный. Используйте его в тех случаях, когда вам нужно выполнить длинную строку операций в некотором контексте, когда любая отдельная операция может потерпеть неудачу в производстве, но где вы не можете / не можете / выиграли '
Альтернатива реализации монадической обработки ошибок заключается в том, чтобы вместо использования объектов пользовательских дескрипторов методы в объекте контекста корректно обрабатывали недействительные идентификаторы дескрипторов. Например, если createText () возвращает -1 в случае сбоя, то любые другие вызовы m_statistics, которые принимают один из этих дескрипторов, должны корректно завершиться, если -1 передается.
Вы также можете поместить ошибку печати в функцию, которая на самом деле не работает. В вашем примере createText (), вероятно, содержит гораздо больше информации о том, что пошло не так, поэтому он сможет вывести в журнал более значимую ошибку. В этом случае есть небольшая польза от передачи ошибок / распечатки для вызывающих. Делайте это, когда вызывающим абонентам необходимо настроить обработку (или использовать внедрение зависимости). Обратите внимание, что наличие внутриигровой консоли, которая может всплывать, когда регистрируется ошибка, является хорошей идеей и помогает и здесь.
Лучший вариант (уже представленный в серии связанных статей выше) для вызовов, которые вы не ожидаете, что произойдет сбой в любой разумной среде - например, простое действие по созданию текстовых BLOB-объектов для системы статистики - просто иметь функцию этот сбой (createText в вашем примере) прерывается. Вы можете быть разумно уверены, что createText () не потерпит неудачу в производственном процессе, если что-то не будет полностью завершено (например, пользователь удалил файлы данных шрифта или по какой-то причине имеет только 256 МБ памяти и т. Д.). Во многих из этих случаев нет даже ничего хорошего, когда сбой случается. Недостаточно памяти? Возможно, вы даже не сможете сделать выделение, необходимое для создания красивой панели с графическим интерфейсом, чтобы показать пользователю ошибку OOM. Отсутствующие шрифты? Затрудняет отображение ошибок для пользователя. Что бы ни случилось,
Просто сбой - это нормально, если вы (а) записываете ошибку в файл журнала и (б) делаете это только на ошибках, которые не вызваны обычными действиями пользователя.
Я бы даже не сказал то же самое для многих серверных приложений, где доступность критична, а сторожевого мониторинга недостаточно, но это сильно отличается от разработки игрового клиента . Я также настоятельно рекомендую вам избегать использования C / C ++, поскольку средства обработки исключений в других языках обычно не похожи на C ++, поскольку они являются управляемыми средами и не имеют всех проблем безопасности исключений, которые есть в C ++. Любые проблемы с производительностью также уменьшаются, поскольку серверы, как правило, ориентированы на параллельность и пропускную способность, а не на минимальные задержки, как игровые клиенты. Даже серверы экшн-игр для шутеров и тому подобное могут функционировать довольно хорошо, например, когда они написаны на C #, поскольку они редко выдвигают аппаратное обеспечение до предела, как это обычно делают клиенты FPS.