Коли і як слід використовувати обробку винятків?


83

Я читаю про обробку винятків. Я отримав деяку інформацію про те, що таке обробка винятків, але у мене є кілька запитань:

  1. Коли кидати виняток?
  2. Замість викиду винятку, чи можемо ми використовувати значення повернення для позначення помилки?
  3. Якщо я захищаю всі свої функції за допомогою блоків try-catch, чи не зменшить це продуктивність?
  4. Коли використовувати обробку винятків?
  5. Я бачив проект, де кожна функція цього проекту містила блок try-catch (тобто код всередині функції оточений блоком try-catch). Це хороша практика?
  6. У чому різниця між try-catch і __try __except?


Вам потрібно буде задати більш конкретні запитання, ніж "Коли кидати виняток". Ви кидаєте виняток, коли трапляється щось виняткове.
Фальмаррі

Я писав про це щодо PHP, але я думаю, що майже все можна застосувати до C ++. Перевірте допис у блозі .
ircmaxell

Відповіді:


96

Ось досить вичерпне керівництво щодо винятків, які, на мою думку, обов’язково потрібно прочитати:

Винятки та обробка помилок - C ++ FAQ або C ++ FAQ lite

Як загальне емпіричне правило, додайте виняток, коли ваша програма може ідентифікувати зовнішнєпроблема, яка перешкоджає виконанню. Якщо ви отримуєте дані від сервера, і ці дані недійсні, видаліть виняток. Не вистачає місця на диску? Киньте виняток. Космічні промені заважають запитувати базу даних? Киньте виняток. Але якщо ви отримуєте якісь недійсні дані з вашої власної програми - не кидайте виняток. Якщо ваша проблема походить від вашого власного поганого коду, краще використовувати ASSERT для захисту від нього. Обробка винятків необхідна для виявлення проблем, з якими програма не може впоратися, та розказує їм про користувача, оскільки користувач може їх вирішити. Але помилки у вашій програмі не є тим, з чим користувач може впоратися, тому збій програми покаже не набагато менше, ніж "Значення answer_to_life_and_universe_and_everything не 42! Це ніколи не повинно відбуватися !!!! 11" виняток.

Спіймайте виняток, коли ви можете зробити з ним щось корисне, наприклад, відобразити вікно повідомлення. Я вважаю за краще ловити виняток один раз у функції, яка якось обробляє введення користувачем. Наприклад, користувач натискає кнопку "Знищити всі гунами", а всередині функції annihilateAllHunamsClicked () є блок try ... catch, щоб сказати "Я не можу". Незважаючи на те, що знищення людства - це складна операція, яка вимагає виклику десятків і десятків функцій, є лише одна спроба ... зловити, тому що для користувача це атомна операція - один клік кнопки. Перевірки винятків у кожній функції є зайвими та потворними.

Крім того, я не можу рекомендувати достатньо ознайомитися з RAII - тобто переконатися, що всі ініціалізовані дані автоматично знищуються. І цього можна досягти, максимально ініціалізувавши стек, а коли потрібно щось ініціалізувати в купі, використовуйте якийсь розумний вказівник. Все ініціалізоване у стеку буде автоматично знищено, коли буде видано виняток. Якщо ви використовуєте тупі вказівники у стилі С, ви ризикуєте витоком пам’яті, коли виникає виняток, оскільки їх ніхто не може очистити за винятком (звичайно, ви можете використовувати вказівники у стилі С як члени вашого класу, але переконайтеся, що вони опікується в деструкторі).


Дякуємо за ваш цінний внесок. > «Обробка винятків необхідна для виявлення проблем, які програма> не може обробити, і повідомляти їх про користувача, тому що користувач може> обробляти їх» Замість видачі винятку я можу повернути значення помилки, а у функції виклику я можу перевірити помилку і відобразити повідомлення, яке допоможе користувачеві впоратися з цим.
Umesha MS

Знову подивіться на поширені запитання, особливо на третій та четвертий фрагменти коду в цьому розділі: parashift.com/c++-faq-lite/exceptions.html#faq-17.2 Винятки є чудовими, оскільки вони дозволяють вашим помилкам з’являтися з будь-якої глибини стека викликів ... Це як якщо коди помилок є батисферами, виняток становлять підводні човни :)
Септаграма

2
@Septagram "Якщо ваша проблема пов'язана з вашим власним поганим кодом, краще використовувати ASSERT для захисту від неї.": Ассамблея не дозволить вам продовжувати завдання, які не покладаються на гілку помилок (лише тому, що ваша програма має помилка в одній функції не означає, що вона не може робити інші корисні речі). Це не дозволить використовувати користувацькі реєстратори. Він обійде деструктори, від яких ви отримаєте вигоду, використовуючи RAII (хочете, щоб ваші буфери файлів були очищені до завершення програми? Краще не пропускайте ці деструктори fcloseтоді!). Це не дасть вам повного стека.
демосвесна

Щось сказати про RAII та "тупі вказівники". Те, що ви використовуєте "німий покажчик", не означає, що ви не стежите за RAII. Пам'ятайте, що лише коли ви розподіляєте системні ресурси, вам потрібно переконатися, що вони звільнені. Покажчик - це просто змінна в стеку, що містить адресу пам'яті. Розподіл та ініціалізація об’єктів не потрібні для використання "тупого вказівника". "Тупий вказівник" може просто містити адресу об'єкта, який виділений в іншому місці, і цілком чудовий для використання. (продовження в наступному коментарі)
SeanRamey

Можливо, у вас є об’єкт Renderer, який зберігає адресу об’єкта Display у вказівнику, щоб намалювати речі на Display, але ви не можете використовувати розумний вказівник, оскільки розумний вказівник знищить об’єкт Display, коли ви знищуєте об’єкт Renderer , чого ви не хочете, щоб сталося. У цьому випадку або "німий покажчик", або посилання є єдиними шляхами.
ШонРамі

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
Yttrill

4

Якщо ваша проблема походить від вашого власного поганого коду, краще використовувати ASSERT для захисту від нього. Обробка винятків необхідна для виявлення проблем, з якими програма не може впоратися, та розказує їм про користувача, оскільки користувач може їх вирішити. Але помилки у вашій програмі не є тим, з чим користувач може впоратися, тому збій програми скаже не так багато

Я не згоден з цим аспектом прийнятої відповіді . Твердження не є зручнішим, ніж створення винятку. Якщо винятки підходили лише для помилок під час виконання (або "зовнішніх проблем"), для чого std::logic_error?

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

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

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

Стандартна бібліотека передбачає винятки з логічних помилок і використовує їх. Вони існують не просто так: оскільки логічні помилки трапляються і є винятковими. Просто тому, що твердження C містить твердження, не є підставою покладатися на такий примітивний (і, можливо, марний) механізм, коли виняток набагато краще справляється з роботою.


3

Найкраще читати для цього

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

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


3
Невже ця стаття насправді не намагається сказати, не використовуйте try/ catch {}?
Крейг МакКвін

2

1. Перевірка винятків включається в код, коли є можливість отримати виняток в результаті або десь між проблемою.

2. Використовуйте блок try-catch лише у тих випадках, коли це потрібно. Використання кожного блоку try-catch додає до додаткової перевірки стану, що, безумовно, зменшує оптимізацію коду.

3.Я думаю, що _try_except - дійсне ім'я змінної ....


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..in2.

    • Якщо ви не спробуєте зловити виняток, я не думаю, що це швидше, це просто просто обхід, якщо errorце сталося, це буде threwдля зовнішнього обробника.

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

Запропонуйте: просто поводитися з собою, коли це просто і логічно.


-3

Багато пуристів C / C ++ взагалі не рекомендують робити винятки. Основні зауваження:

  1. Це повільно - звичайно, це насправді не "повільно". Однак, порівняно із чистим c / c ++, є накладні витрати.
  2. Він вводить помилки. Якщо ви неправильно обробляєте винятки, ви можете пропустити код очищення у функції, яка видає виняток.

Натомість перевіряйте повернене значення / код помилки кожного разу, коли ви викликаєте функцію.


15
Нісенітниця. По-перше, ми говоримо про C ++ - "C / C ++" не існує, і, звичайно, програмістам, що працюють лише на C, буде незручно за винятками. "чистий C / C ++" не існує, а винятки є частиною "чистого C ++" - вони входять до стандарту. Ви можете написати помилковий код обробки винятків, а також помилковий код, що повертає помилку, або код стану помилки помилки - оманливо характеризувати винятки як такі, що в більшій мірі схильні до помилок. Вибачте, але це одна з найгірших порад, яку я бачив у SO
Тоні Делрой

1
Позначте мене, якщо хочете, але, походячи із рівня С, я можу сказати вам, що я та багато інших людей утримуємось від використання виключень взагалі.
speedplane

4
1. Це повільно лише тоді, коли насправді видається виняток. А винятки називаються винятками, оскільки їх викидають лише у виняткових обставинах. Програма, що викидає понад 9000 винятків за секунду, робить це неправильно. 2. Якщо ви обережно не керуєте пам'яттю за допомогою чистого нового видалення, очищення буде виконано за вас. Управління пам'яттю у стилі С набагато частіше схильне до помилок, ніж винятки. 3. Перевірка поверненого значення додає багато зайвих рядків коду, тим самим роблячи його менш читабельним і менш ремонтопридатним.
Септаграма

3
1) залежно від компілятора, що може бути неправдою. І всі винятки додають велику кількість байт-коду, що збільшує розмір виконуваного файлу та сповільнює роботу. 2) постійно використовується чисте нове видалення, не все може бути об’єктом стека. 3) перевірка повернутих значень може бути більшою кількістю рядків коду, але це, можливо, більш ремонтопридатне. Ви можете не погодитися, але загальновизнаним є обґрунтоване переконання, що у багатьох випадках винятки на C ++ є поганими. Погляньте на приклад embedded-C ++.
speedplane

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