Широко ли используются исключения в дизайне игрового движка или предпочтительнее использовать операторы if? Например с исключениями:
try {
m_fpsTextId = m_statistics->createText( "FPS: 0", 16, 20, 20, 1.0f, 1.0f, 1.0f );
m_cpuTextId = m_statistics->createText( "CPU: 0%", 16, 20, 40, 1.0f, 1.0f, 1.0f );
m_frameTimeTextId = m_statistics->createText( "Frame time: 0", 20, 20, 60, 1.0f, 1.0f, 1.0f );
m_mouseCoordTextId = m_statistics->createText( "Mouse: (0, 0)", 20, 20, 80, 1.0f, 1.0f, 1.0f );
m_cameraPosTextId = m_statistics->createText( "Camera pos: (0, 0, 0)", 40, 20, 100, 1.0f, 1.0f, 1.0f );
m_cameraRotTextId = m_statistics->createText( "Camera rot: (0, 0, 0)", 40, 20, 120, 1.0f, 1.0f, 1.0f );
} catch ... {
// some info about error
}
и с ifs:
m_fpsTextId = m_statistics->createText( "FPS: 0", 16, 20, 20, 1.0f, 1.0f, 1.0f );
if( m_fpsTextId == -1 ) {
// show error
}
m_cpuTextId = m_statistics->createText( "CPU: 0%", 16, 20, 40, 1.0f, 1.0f, 1.0f );
if( m_cpuTextId == -1 ) {
// show error
}
m_frameTimeTextId = m_statistics->createText( "Frame time: 0", 20, 20, 60, 1.0f, 1.0f, 1.0f );
if( m_frameTimeTextId == -1 ) {
// show error
}
m_mouseCoordTextId = m_statistics->createText( "Mouse: (0, 0)", 20, 20, 80, 1.0f, 1.0f, 1.0f );
if( m_mouseCoordTextId == -1 ) {
// show error
}
m_cameraPosTextId = m_statistics->createText( "Camera pos: (0, 0, 0)", 40, 20, 100, 1.0f, 1.0f, 1.0f );
if( m_cameraPosTextId == -1 ) {
// show error
}
m_cameraRotTextId = m_statistics->createText( "Camera rot: (0, 0, 0)", 40, 20, 120, 1.0f, 1.0f, 1.0f );
if( m_cameraRotTextId == -1 ) {
// show error
}
Я слышал, что исключения немного медленнее, чем ifs, и я не должен смешивать исключения с методом if-check. Однако с исключениями у меня более читаемый код, чем с тоннами if после каждого метода initialize () или чего-то подобного, хотя они, на мой взгляд, иногда слишком тяжелы для одного метода. Они подходят для разработки игр или лучше придерживаться простых ifs?
Ответы:
Краткий ответ: прочитайте Обработка чувствительных ошибок 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.
источник
warn_unused_result
в некоторых компиляторах есть возможность добавить атрибут like функции, который позволяет отлавливать необработанный код ошибки.Старая мудрость самого Дональда Кнута такова:
Распечатайте это как большой плакат и повесьте его везде, где вы занимаетесь серьезным программированием.
Так что даже если try / catch немного медленнее, я бы использовал его по умолчанию:
Код должен быть максимально читаемым и понятным. Вы могли бы написать код один раз, но вы будете читать его много раз для отладки этого кода, для отладки другого кода, для понимания, для улучшения, ...
Корректность по производительности. Делать вещи if / else правильно не тривиально. В вашем примере это сделано неправильно, потому что может отображаться не одна ошибка, а множественная. Вы должны использовать каскадный if / then / else.
Легко использовать последовательно: Try / catch охватывает стиль, в котором вам не нужно проверять ошибки в каждой строке. С другой стороны: один пропущен, если / else, и ваш код может привести к хаосу. Конечно, только в невоспроизводимых обстоятельствах.
Итак, последний пункт:
Я слышал об этом около 15 лет назад, не слышал об этом ни из каких достоверных источников в последнее время. Компиляторы могли улучшиться или что угодно.
Суть в том, чтобы не делать преждевременной оптимизации. Делайте это только тогда, когда вы можете доказать с помощью бенчмарка, что код под рукой является жестким внутренним циклом некоторого интенсивно используемого кода и что стиль переключения значительно повышает производительность . Это случай 3%.
источник
На этот вопрос уже был действительно отличный ответ, но я хотел бы добавить некоторые соображения о читабельности кода и исправлении ошибок в вашем конкретном случае.
Я считаю, что ваш код может выглядеть так:
Нет исключений, нет, если. Сообщение об ошибке может быть сделано в
createText
. ИcreateText
может вернуть идентификатор текстуры по умолчанию, который не требует проверки возвращаемого значения, так что остальная часть кода работает так же хорошо.источник