Урок №128. Деструктори

  Юрій  | 

  Оновл. 3 Вер 2021  | 

 256

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

Деструктори

Деструктор — це спеціальний тип методу класу, який виконується при видаленні об’єкта класу. У той час як конструктори призначені для ініціалізації класу, деструктори призначені для очищення пам’яті після нього.

Коли об’єкт автоматично виходить з області видимості або динамічно виділений об’єкт явно видаляється за допомогою ключового слова delete, викликається деструктор класу (якщо він існує) для виконання необхідного очищення до того, як об’єкт буде видалений з пам’яті. Для простих класів (тих, які тільки ініціалізують значення звичайних змінних-членів) деструктор не потрібен, так як C++ автоматично виконає очищення самостійно.

Проте, якщо об’єкт класу містить будь-які ресурси (наприклад, динамічно виділену пам’ять або файл/базу даних), або, якщо вам необхідно виконати будь-які дії до того, як об’єкт буде знищений, деструктор є ідеальним рішенням, оскільки він виконує останні дії з об’єктом перед його остаточним знищенням.

Імена деструкторів

Так само, як і конструктори, деструктори мають свої правила, які стосуються їхніх імен:

   деструктор повинен мати те ж ім’я, що і клас, зі знаком тильда (~) на самому початку;

   деструктор не може приймати аргументи;

   деструктор не має типу повернення.

З другого правила випливає ще одне правило: для кожного класу може існувати тільки один деструктор, так як немає можливості перевантажити деструктори, як функції, і відрізнятися один від одного аргументами вони не можуть.

Приклад використання деструктора на практиці

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

Результат виконання програми:

The value of element 7 is 8

У першому рядку функції main() ми створюємо новий об’єкт класу Massiv з ім’ям arr і передаємо довжину (length) 15. Це призводить до виклику конструктора, який динамічно виділяє пам’ять для масиву класу (m_array). Ми повинні тут використовувати динамічне виділення, оскільки на момент компіляції ми не знаємо довжини масиву (це значення нам передає caller).

В кінці функції main() об’єкт arr виходить з області видимості. Це призводить до виклику деструктора ~Massiv() і до видалення масиву, який ми виділили раніше в конструкторі!

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

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

Результат виконання програми:

Constructing Another 1
1
Constructing Another 2
2
Destructing Another 2
Destructing Another 1

Зверніть увагу, Another 1 знищується після Another 2, так як ми видалили pObject до завершення виконання функції main(), тоді як об’єкт object не був видалений до кінця main().

Ідіома програмування RAII

Ідіома RAII (англ. «Resource Acquisition Is Initialization» = «Отримання ресурсу є ініціалізація») — це ідіома об’єктно-орієнтованого програмування, при якій використання ресурсів прив’язується до часу життя об’єктів з автоматичною тривалістю життя. У мові C++ ідіома RAII реалізується через класи з конструкторами і деструкторами. Ресурс (наприклад, пам’ять, файл або база даних) зазвичай отримується в конструкторі об’єкта (хоча цей ресурс може бути отриманий і після створення об’єкта, якщо в цьому є сенс). Потім цей ресурс можна використовувати, поки об’єкт живий. Ресурс звільняється в деструкторі при знищенні об’єкта. Основною перевагою RAII є те, що це допомагає запобігти витоку ресурсів (наприклад, пам’яті, яка не була звільнена), так як всі об’єкти, що містять ресурси, автоматично очищаються.

В рамках ідіоми програмування RAII об’єкти, які мають ресурси, не повинні динамічно виділятися, так як деструктори викликаються тільки при знищенні об’єктів. Для об’єктів, виділених зі стеку, це відбувається автоматично, коли об’єкт виходить з області видимості, тому немає необхідності турбуватися про те, що ресурс в кінцевому результаті не буде очищений. Однак за очистку динамічно виділених об’єктів, які виділяються з купи, вже користувач несе відповідальність: якщо він забув її виконати, деструктор викликатися не буде, і пам’ять як для об’єкта класу, так і для керованого ресурсу буде втрачена — станеться витік пам’яті!

Клас Massiv з програми, наведеної на початку цього уроку, є прикладом класу, який реалізує принципи RAII: виділення в конструкторі, звільнення в деструкторі. std::string і std::vector — це приклади класів зі Стандартної бібліотеки С++, які слідують принципам RAII: динамічна пам’ять виділяється при ініціалізації і автоматично звільняється при знищенні.

Правило: Використовуйте ідіому програмування RAII і не виділяйте об’єкти вашого класу динамічно.

Попередження про функцію exit()

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

Висновки

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

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

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

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

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