Урок №192. Неспіймані винятки і обробники catch-all

  Юрій  | 

  Оновл. 27 Бер 2021  | 

 32

До цього моменту ви вже повинні були розібратися з тим, як працюють винятки в мові С++. На цьому уроці ми розглянемо ще кілька цікавих тем, пов’язаних з винятками.

Неспіймані винятки

На попередньому уроці ми навчилися делегувати обробку винятків caller-у (або іншій функції, яка “знаходиться вище” в стеці викликів). У наступному прикладі функція mySqrt() викидає виняток і припускає, що його хтось обробить. Але що станеться, якщо цього ніхто не зробить?

Ось наша програма обчислення квадратного кореня числа без блоку try в функції main():

Тепер припустимо, що користувач ввів -5, і mySqrt(-5) згенерувало виняток. Функція mySqrt() не обробляє свої винятки самостійно, тому стек починає розкручуватися, і точка виконання повертається назад в функцію main(). Але, оскільки в main() також немає обробника винятків, виконання main() і всієї програми припиняється.

Коли main() завершує своє виконання з необробленим винятком, то операційна система зазвичай повідомляє нас про те, що відбулася помилка необробленого винятку. Як вона це зробить — залежить від кожної операційної системи окремо:

   або виведе повідомлення про помилку;

   або відкриє діалогове вікно з помилкою;

   або просто збій.

Це те, що ми не повинні допускати, як програмісти!

Обробники всіх типів винятків

А тепер загадка: “Функції можуть генерувати винятки будь-якого типу даних, і, якщо виняток не спіймано, це призведе до розкручування стеку і потенційному завершенню виконання програми. Оскільки ми можемо викликати функції, не знаючи їх реалізації (і, відповідно, які винятки вони можуть генерувати), то як ми можемо цьому запобігти?”.

На щастя, мова C++ надає нам механізм виявлення/обробки всіх типів винятків — обробник catch-all. Обробник catch-all працює так само, як і звичайний блок catch, за винятком того, що замість обробки винятків певного типу даних, він використовує еліпсис (...) в якості типу даних.

А як ми вже знаємо, еліпсиси можуть використовуватися для передачі аргументів будь-якого типу даних в функцію. У цьому контексті вони представляють собою винятки будь-якого типу даних. Ось простий приклад:

Оскільки для типу int не існує спеціального обробника catch, то обробник catch-all ловить цей виняток. Отже, результат:

We caught an exception of an undetermined type!

Обробник catch-all повинен знаходитися останнім в ланцюжку блоків catch. Це робиться для того, щоб винятки спочатку могли бути спіймані обробниками catch, адаптованими до конкретних типів даних (якщо вони взагалі існують). У Visual Studio це контролюється, щодо інших компіляторів не впевнений, чи є таке обмеження.

Часто блок обробника catch-all залишають порожнім:

Цей обробник ловить будь-які непередбачені винятки і запобігає розкручуванню стеку (і потенційному завершенню виконання програми), але тут він не виконує ніякої обробки винятків.

Використання обробника catch-all в функції main()

Розглянемо наступну програму:

В цьому випадку, якщо функція runGame() або будь-яка інша з функцій, які викликаються в runGame(), викине виняток, який не буде спіймано функціями в стеці вище, то, в кінцевому підсумку, він потрапить в обробник catch-all. Це запобіжить завершенню виконання функції main() і дасть нам можливість вивести повідомлення із зазначенням помилки на наш розсуд, а потім зберегти стан користувача до виходу з програми. Це може бути корисно для виявлення і усунення непередбачених проблем.

Специфікації винятків

Цю тему можете розглядати як додаткове чтиво, тому що специфікації винятків рідко використовуються на практиці, погано підтримуються компіляторами, а Б’ярн Страуструп (творець мови C++) вважає їх невдалим експериментом.

Специфікації винятків — це механізм оголошення функцій із зазначенням того, чи генеруватиме функція винятки (і які саме) чи ні. Це може бути корисно при визначенні необхідності розміщення виклику функції в блоці try.

Існують три типи специфікації винятків, кожен з яких використовує так званий синтаксис throw (…).

По-перше, ми можемо використовувати порожній оператор throw для позначення того, що функція не генерує ніяких винятків, які виходять за її межі:

Зверніть увагу, функція doSomething() все ще може генерувати винятки, тільки обробляти вона повинна їх самостійно. Будь-яка функція, оголошена з використанням throw() (як у вищенаведеному прикладі), повинна негайно припинити виконання програми, якщо вона спробує згенерувати виняток, який призведе до розкручування стеку. Іншими словами, ми повідомляємо, що всі винятки функції doSomething(), функція doSomething() оброблятиме самостійно.

По-друге, ми можемо використовувати оператор throw із зазначенням типу винятку, який може генерувати ця функція:

Нарешті, ми можемо використовувати еліпсис з оператором throw для позначення того, що функція може генерувати різні типи винятків:

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

Оцінити статтю:

1 Зірка2 Зірки3 Зірки4 Зірки5 Зірок (Немає Оцінок)
Loading...

Залишити відповідь

Ваш E-mail не буде опублікований. Обов'язкові поля відмічені *