Деструкторы в Ruby
May 20, 2011 in Ruby on RailsЯ учил объектно-ориентированное программирование на 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, потому использование их в нормальных целях не рекомендуется.
at_exit
Для выполнения действий по завершению работы программы существует метод Kernel#at_exit. Он добавляет произвольный блок кода в очередь того, что будет выполнено в конце программы.
Как at_exit
сочетается с исключениями? Проверим:
at_exit do
puts 'at_exit'
end
begin
raise 'Hell'
ensure
puts 'ensure'
end
Результат работы:
ensure
at_exit
/home/leonid/at_exit.rb:14: Hell (RuntimeError)
Как видишь, во-первых, исключения не мешают работе at_exit
, а во-вторых, блок ensure
логично выполняется до блока at_exit
.
Поэтому у меня Headless обычно подключается так:
headless = Headless.new
at_exit do
headless.destroy
end
Блочный синтаксис
Еще один удобный способ очищать ресурсы – оборачивать их использование в блок. Например, File.open. Или Headless.ly
:
Headless.ly do
# hot window-on-window action
end
Внутри это выглядит так:
def self.ly
headless = Headless.start
yield
headless.destroy
end
Понравился пост? Купи мне кофе