Урок №196. Нюанси та недоліки використання винятків

  Юрій  | 

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

 90

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

Очищення пам’яті

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

Що відбудеться, якщо функція writeFile() не спрацює і викине об’єкт класу-винятку FileException? До цього моменту ми вже відкрили файл, і точка виконання перейде до обробника catch, який виведе помилку і завершить своє виконання. Зверніть увагу, операція закриття файлу closeFile(filename) ніколи не виконається! Цей код повинен бути переписаний наступним чином:

Такий тип помилок часто виникає і в іншій формі при роботі з динамічним виділенням пам’яті:

Якщо функція processPerson() викине виняток, то точка виконання перейде до обробника catch. В результаті, пам’ять, виділена alex, ніколи не звільниться! Цей приклад складніший, ніж попередній, оскільки alex знаходиться всередині блоку try, і виходить з області видимості при завершенні виконання блоку try. Це означає, що обробник винятків взагалі не може отримати доступ до alex (цей об’єкт до моменту виконання блоку catch вже буде знищений), тому можливості звільнити пам’ять у нас немає.

Однак є два відносно простих способи це виправити. По-перше, потрібно оголосити alex поза блоком try, щоб він не знищувався при завершенні виконання блоку try:

Оскільки alex оголошений поза блоком try, то він доступний як в блоці try, так і в обробнику catch. Це означає, що обробник catch зможе виконати очистку пам’яті належним чином.

Другий спосіб — локально використовувати об’єкт класу, який самостійно виконує після себе очищення пам’яті при виході з області видимості (його ще називають «розумним вказівником»). Стандартна бібліотека C++ надає клас std::unique_ptr, який може використовуватися в цих цілях. std::unique_ptr — це шаблон класу, який містить вказівник і звільняє виділену йому пам’ять при виході з області видимості.

Ми поговоримо більше про розумні вказівники на відповідних уроках.

Винятки і деструктори

На відміну від конструкторів, де генерація винятків може бути корисним способом вказати, що створити об’єкт не вдалося, винятки ніколи не повинні генеруватися в деструкторах.

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

Отже, найкраще, що ви можете зробити — це НЕ використовувати винятки в деструкторах. Краще замість цього записати помилку в лог-файл.

Проблеми з продуктивністю

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

Примітка: Деякі сучасні комп’ютерні архітектури підтримують модель винятків “Винятки з нульовою вартістю” (або “Винятки zero-cost”). Винятки з нульовою вартістю, якщо вони підтримуються, не вимагають додаткових витрат при виконанні програми в разі відсутності помилок (саме в цьому випадку ми найбільше дбаємо про продуктивність). Проте, винятки з нульовою вартістю споживають ще більше ресурсів в разі виявлення винятку.

У яких випадках слід використовувати винятки?

Винятки та їх обробку найкраще використовувати, якщо виконуються всі наступні умови:

   Оброблювана помилка виникає рідко.

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

   Помилка не може бути оброблена в тому місці, де вона виникає.

   Немає хорошого альтернативного способу повернути код помилки назад в caller.

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

Давайте розберемося:

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

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

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

З огляду на все вищесказане ви повинні навчитися розуміти і відрізняти ризики і користь від використання (і обробки) винятків.

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

1 Зірка2 Зірки3 Зірки4 Зірки5 Зірок (2 оцінок, середня: 5,00 з 5)
Loading...

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

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