Спробуйте-catch або ifs для керування помилками в C ++


30

Чи широко використовуються винятки в дизайні ігрового двигуна, або переважніше використовувати чисті, якщо твердження? Наприклад, з винятками:

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, і я не повинен змішувати винятки з методом перевірки. Однак, за винятком, у мене є більш читабельний код, ніж у тонах ifs після кожного методу ініціалізації () або чогось подібного, хоча вони, на мою думку, іноді занадто важкі. Чи все в порядку для розробки ігор чи краще дотримуватися простих ifs?


Багато людей стверджують, що Boost поганий для розвитку ігор, але я рекомендую вам переглянути ["Boost.optional"] ( boost.org/doc/libs/1_52_0/libs/optional/doc/html/index.html ) це зробить ваш приклад трохи приємніше
ManicQin

Хороша розмова про помилки Андрія Олександреску, я без винятку використовував подібний підхід до його.
Фельвін

Відповіді:


48

Коротка відповідь: читання Розумною Обробка помилок 1 , Розумна Обробка помилок 2 , і обслуговування Розумна Помилки 3 Нікласа Frykholm. Насправді, читайте всі статті цього блогу, поки ви в цьому. Не скажу, що я з усім згоден, але більшість це золото.

Не використовуйте винятки. Є безліч причин. Я перерахую основні з них.

Вони справді можуть бути повільнішими, хоча це зменшується на нових компіляторах. Деякі компілятори підтримують "нульові накладні винятки" для кодових шляхів, які насправді не викликають виключення (хоча це трохи брехня, оскільки все ще є додаткові дані, необхідні для обробки винятків, роздуття вашого розміру виконуваного файлу / dll). Кінцевий результат, однак, полягає в тому, що так, використання винятків відбувається повільніше, і в будь-яких критичних кодах до ефективності слід абсолютно уникати їх. Залежно від вашого компілятора, включення їх взагалі може додавати накладні витрати. Вони завжди завжди розширюють розмір коду, доволі значно у багатьох випадках, що може сильно вплинути на продуктивність сучасного обладнання.

Винятки роблять код набагато крихкішим. Існує сумнозвісна графіка (яку я, на жаль, зараз не можу знайти), яка в основному просто показує на графіку складність написання безпечного для винятку коду проти винятку-небезпечного коду, а перший - значно більший штрих. За винятком просто багато маленьких діточок, і існує безліч способів написання коду, який виглядає безпечним, але насправді це не так. Навіть увесь комітет C ++ 11 замислився на цьому і забув додати важливі допоміжні функції для правильного використання std :: unique_ptr, і навіть за допомогою цих допоміжних функцій потрібно більше вводити текст, щоб використовувати їх, ніж не, і більшість програмістів виграли ' навіть не розумію, що не так, якщо вони цього не роблять.

Більш конкретно для ігрової індустрії, деякі компілятори / програми виконання постачальників консолей прямо не підтримують винятки повністю або взагалі не підтримують їх. Якщо ваш код використовує винятки, можливо, вам все ж доведеться переписувати частини свого коду, щоб перенести його на нові платформи. (Не впевнений, що це змінилося за 7 років з моменту випуску зазначених консолей; ми просто не використовуємо винятки, вони навіть відключені в налаштуваннях компілятора, тому я не знаю, що хтось, з ким я спілкуюся, навіть нещодавно перевіряв.)

Загальна лінія мислення досить чітка: використовуйте винятки для виняткових обставин. Використовуйте їх, коли ваша програма потрапляє в "Я поняття не маю, що робити, можливо, сподіваюся, це зробить хтось інший, тому я кину виняток і побачу, що станеться". Використовуйте їх, коли жоден інший варіант не має сенсу. Використовуйте їх, коли вам не байдуже, якщо ви випадково просочилися небагато пам’яті або не вдалося очистити ресурс, оскільки ви заплутали використання належних розумних ручок. У всіх інших випадках не використовуйте їх.

Що стосується коду, як ваш приклад, у вас є кілька інших способів виправити проблему. Один з найбільш надійних - хоча і не обов'язково найбільш ідеальний у вашому простому прикладі - це схилятися до монадичних типів помилок. Тобто createText () може повернути власний тип обробки, а не ціле число. Цей тип ручки має аксесуари для оновлення або контролю тексту. Якщо ручка буде переведена в стан помилки (тому що createText () не вдалося), то подальший виклик на ручку просто виходить з ладу. Ви також можете запитати ручку, щоб побачити, чи вона помилилася, і якщо так, то яка остання помилка. Цей підхід має більше накладних витрат, ніж інші варіанти, але він досить солідний. Використовуйте його у тих випадках, коли вам потрібно виконати довгу низку операцій у певному контексті, коли будь-яка окрема операція може бути невдалою у виробництві, але коли ви не / не можете / виграли '

Альтернативою реалізації монадичної обробки помилок є, замість того, щоб використовувати власні об'єкти обробки, щоб методи на контекстному об'єкті витончено боролися з недійсними ідентифікаторами обробки. Наприклад, якщо createText () повертає -1, коли він не працює, то будь-які інші виклики до m_statistics, які беруть одну з цих ручок, повинні витончено закриватися, якщо -1 передано в.

Ви також можете поставити помилку друку всередині функції, яка насправді не працює. У вашому прикладі createText (), ймовірно, має більше інформації про те, що пішло не так, тож ви зможете скинути в журнал більш значущу помилку. Немало користі в цьому випадку для виштовхування помилок на обробку / друкування помилок. Зробіть це, коли абонентам потрібно налаштувати обробку (або використовувати ін'єкцію залежності). Зауважте, що наявність ігрової консолі, яка може з’являтись під час реєстрації помилки, є хорошою ідеєю і тут також допомагає.

Найкращий варіант (вже представлений у зв'язаній серії статей вище) для дзвінків, які ви не очікуєте збою в будь-якому розумному середовищі - наприклад, простий акт створення текстових крапок для системи статистики - просто мати функцію що не вдалося (createText у вашому прикладі) скасувати. Ви можете бути впевнені, що createText () не вийде з ладу у виробництві, якщо щось не виграє (наприклад, користувач видалив файли даних шрифтів або чомусь має лише 256 МБ пам'яті тощо). У багатьох із цих випадків навіть не дуже добре робити те, коли відбувається збій. Недостатньо помяті? Можливо, ви навіть не зможете зробити виділення, необхідне для створення приємної панелі GUI, щоб показати користувачеві помилку OOM. Відсутні шрифти? Утрудняє відображення помилок користувачеві. Що б не було,

Просто збій є цілком нормальним, доки ви (a) записуєте помилку у файл журналу та (b) робите це лише на помилках, які не викликаються звичайними діями користувача.

Я б не сказав те ж саме для багатьох серверних додатків, де доступність є критичною, а моніторинг сторожового контролю недостатній, але це зовсім інше, ніж розробка клієнтських ігор . Я також наполегливо уникатимуть використання C / C ++ там, оскільки засоби оброблення винятків інших мов, як правило, не кусаються, як C ++, оскільки вони керовані середовищем і не мають усіх проблем із безпекою для винятку, які мають C ++. Будь-які занепокоєння щодо продуктивності також зменшуються, оскільки сервери, як правило, зосереджуються більше на паралельності та пропускній спроможності, ніж на мінімальних гарантіях затримки, як ігрові клієнти. Навіть сервери ігрових дій для шутерів тощо можуть працювати досить добре, коли вони написані на C #, оскільки вони рідко підштовхують обладнання до своїх меж, як це роблять клієнти FPS.


1
+1. Крім того, є можливість додати atribute like warn_unused_resultto function у деяких компіляторах, що дозволяє ловити не оброблений код помилки.
Мацей П'єхотка

Приїхав сюди, побачивши C ++ у назві, і був абсолютно впевнений, що монадійна робота з помилками вже не буде тут. Хороший матеріал!
Адам

як щодо місць, де продуктивність не є критичною, і ви, головним чином, прагнете швидкої реалізації? наприклад, коли ви реалізуєте логіку гри?
Ali1S232

а як же ідея використовувати обробку виключень лише для налагодження? як, коли ви випускаєте гру, ви очікуєте, що все пройде без проблем. тому ви використовуєте винятки для пошуку та виправлення помилок під час розробки, а пізніше в режимі випуску видаліть усі ці винятки.
Ali1S232

1
@Gajoo: для логіки гри винятки просто ускладнюють логіку (як це ускладнює дотримання всіх кодів). Навіть у Pythnn та C # винятки є рідкісними для логіки гри, на мій досвід. для налагодження жорстке твердження, як правило, набагато зручніше. Перервіться та зупиніться в той самий момент, коли щось піде не так, не після розкручування великих порцій стеку та втрати всілякої контекстної інформації через семантику викидів, що викидаються. Для логіки налагодження потрібно створити інструменти та інформацію / редагувати графічні інтерфейси, щоб дизайнери могли перевіряти та змінювати налаштування.
Шон Міддлічч

13

Стара мудрість самого Дональда Кнута:

"Ми повинні забути про невелику неефективність, скажімо, про 97% часу: передчасна оптимізація - корінь усього зла".

Роздрукуйте це як великий плакат і повісьте його де завгодно серйозного програмування.

Тож навіть якщо пробувати / ловити трохи повільніше, я б використовував це за замовчуванням:

  • Код повинен бути максимально читабельним і зрозумілим. Ви могли б написати код один раз , але ви будете прочитати це багато разів більше для налагодження цього коду для налагодження іншого коду, для розуміння, для поліпшення ...

  • Коректність щодо продуктивності. Робити правильно if / else речі не є тривіальним. У вашому прикладі це зроблено неправильно, тому що не одна помилка може бути показана, а кратна. Ви повинні використовувати каскадні if / then / else.

  • Простіше використовувати послідовно: Спробуйте / catch охоплює стиль, у якому не потрібно перевіряти на помилки кожного рядка. З іншого боку: один зниклий, якщо / else, і ваш код може призвести до аварії. Звичайно лише в невідтворюваних обставинах.

Отже, останній пункт:

Я чув, що винятки трохи повільніше, ніж якщо

Я чула про це близько 15 років тому, останнім часом не чула цього з жодних достовірних джерел. Можливо, компілятори вдосконалилися чи будь-що інше.

Основний момент: Не допускайте передчасної оптимізації. Робіть це тільки тоді , коли ви можете довести бенчмарка , що код під рукою щільний внутрішній контур деякій мірі використовуваного коду і що перемикання стиль підвищує продуктивність значно . Це 3% випадків.


22
Я -1 це до нескінченності. Я не можу зрозуміти шкоду, яку ця цитата Кнут заподіяла мізкам розробників. Зважаючи на те, чи використовувати винятки, це не передчасна оптимізація, це важливе дизайнерське рішення, яке має негативні наслідки.
sam hocevar

3
@SamHocevar: Я шкодую, я не розумію вашу точку зору: Питання це про можливу втрату продуктивності при використанні виключень. Моя думка: Не думайте про це, хіт не такий вже й поганий (якщо такий є), а інші речі набагато важливіші. Тут ви, мабуть, погоджуєтесь, додавши "моє рішення" до мого списку. ДОБРЕ. З іншого боку, ви говорите, що цитата Кнута погана, тому що передчасна оптимізація не є поганою. Але саме це сталося тут ІМО: Q не думає про архітектуру, дизайн або різні алгоритми, лише про винятки та їх вплив на продуктивність.
AH

2
Я б не погоджувався з чіткістю в C ++. У C ++ є неперевірені винятки, тоді як деякі компілятори реалізують перевірені значення повернення. У результаті у вас з'являється код, який виглядає чіткіше, але, можливо, прихована пам'ять протікає в пам'яті або навіть ставиться код у невизначений стан Також сторонні код не може бути безпечним для виключень. (У Java / C # ситуація інша, яка перевірила винятки, GC, ...). Що стосується дизайнерського рішення - якщо вони не перетинають точки API, рефакторинг від / до кожного стилю може бути виконаний напівавтоматично за допомогою perl one-liners.
Мацей П'єхотка

1
@SamHocevar: " Питання в тому, що краще, а не те, що швидше. " Перечитайте останній абзац питання. Тільки причина , він навіть подумує про те, не використовуючи виключення, тому що він думає , що вони можуть бути повільніше. Тепер, якщо ви думаєте, що є й інші проблеми, які ОП не врахували, не соромтеся опублікувати їх або висловити позицію тим, хто це зробив. Але ОП дуже чітко зосереджена на виконанні винятків.
Нікол Болас

3
@AH - якщо ти збираєшся цитувати Knuth, тоді не забувай решту (і IMO - найважливішу частину): " Все ж ми не повинні передавати наші можливості на цих критичних 3%. Хорошого програміста не буде Заспокоєний таким поміркуванням, він буде розумним уважно переглянути критичний код, але лише після того, як цей код буде визначений ". Занадто часто хтось сприймає це як привід не робити оптимізацію взагалі або намагатися виправдати, що код повільний у випадках, коли продуктивність не є оптимізацією, а насправді є основною вимогою.
Максим Мінімус

10

На це запитання вже була дійсно чудова відповідь, але я хотів би додати деякі думки щодо читабельності коду та відновлення помилок у вашому конкретному випадку.

Я вважаю, що ваш код може насправді виглядати так:

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 );

Ні винятків, ні ifs. Повідомлення про помилки можна зробити в createText. І createTextможе повернути ідентифікатор текстури за замовчуванням, який не вимагає від вас перевірки значення повернення, щоб решта коду працювала так само добре.

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.