Деструктори в Ruby - Leonid Shevtsov

Я вчив об'єктно-орієнтоване програмування на C++. У C++ одним із ключових понять ОВП був деструктор – метод, який звільнить ресурси після видалення об'єкта. Деструктори були й у PHP.

А в Ruby деструкторів немає (як, мабуть, і в багатьох інших мовах).

Причина відсутності деструкторів у Ruby

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

Звільненням у пам'яті в Ruby займається збирач сміття. Неможливо примусово видалити об'єкт із пам'яті. Впровадження такої можливості змусило б турбуватися про биті покажчики а-ля C++. Можна примусово запустити збір сміття шляхом ObjectSpace.garbage_collect, але це не гарантує видалення конкретного об'єкта.

Тому своєчасне звільнення ресурсів неможливо гарантувати.

Ось через невизначеність видалення об'єктів у Ruby немає деструкторів.

Альтернативи деструкторам у Ruby

Наведу приклад з мого гема headless. Він запускає віртуальний X-сервер xvfb. Зрозуміло, що цей сервер необхідно рано чи пізно зупинити, причому вручну. Як вирішити таке завдання?

Деструктори за згодою

“Давайте метод FooClass#destroy звільнятиме ресурси”.

Ця угода в Ruby не існує, наприклад, метод ActiveRecord::Base#destroy не звільняє ресурси, а видаляє запис з бази (причому ресурси самого об'єкта він не особливо чекає взагалі).

Проте за бажання можна його ввести для своїх класів. Простийприклад - метод File.close.

Фіналізатори

Якщо дуже хочеться виконати дію після видалення об'єкта, то в Ruby є фіналізатори: ObjectSpace.define_finalizer.

Слід зазначити, що фіналізатори виконуються після видалення об'єкта, крім того, неможливо гарантувати, ні спровокувати запуск фіналізатора.

Крім того, фіналізатори впливають на глибокі нутрощі віртуальної машини Ruby, тому використання їх у нормальних цілях не рекомендується.

Для виконання дій із завершення роботи програми існує метод Kernel#at_exit. Він додає довільний блок коду у чергу того, що буде виконано наприкінці програми.

Як at_exit узгоджується з винятками? Перевіримо:

Як бачиш, по-перше, винятки не заважають роботі at_exit, а по-друге, блок ensure логічно виконується до блоку at_exit.

Тому у мене Headless зазвичай підключається так: