Деструктори в 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 зазвичай підключається так: