Руби: Деструкторы? - PullRequest
24 голосов
/ 11 мая 2011

Мне нужно время от времени создавать изображения с помощью rmagick в директории кэша.

Чтобы потом быстро избавиться от них, не теряя их для просмотра, я хочу удалить файлы изображений, пока мой экземпляр Rubyобъект Image-Class разрушен или входит в сборщик мусора.

Какой ClassMethod я должен перезаписать, чтобы заполнить деструктор кодом?

Ответы [ 7 ]

25 голосов
/ 20 декабря 2012

@ Решение edgerunner почти сработало.По сути, вы не можете создать замыкание вместо вызова define_finalizer, так как при этом фиксируется привязка текущего self.В Ruby 1.8 кажется, что вы не можете использовать любой proc объект, преобразованный (используя to_proc) из метода, который связан с self.Чтобы заставить его работать, вам нужен объект proc, который не захватывает объект, для которого вы определяете финализатор.

class A
  FINALIZER = lambda { |object_id| p "finalizing %d" % object_id }

  def initialize
    ObjectSpace.define_finalizer(self, self.class.method(:finalize))  # Works in both 1.9.3 and 1.8
    #ObjectSpace.define_finalizer(self, FINALIZER)                    # Works in both
    #ObjectSpace.define_finalizer(self, method(:finalize))            # Works in 1.9.3
  end

  def self.finalize(object_id)
    p "finalizing %d" % object_id
  end

  def finalize(object_id)
    p "finalizing %d" % object_id
  end
end

a = A.new
a = nil

GC.start
20 голосов
/ 11 мая 2011

Вы можете использовать ObjectSpace.define_finalizer при создании файла изображения, и он будет вызван, когда мусорщик придет собирать. Только будьте осторожны, чтобы не ссылаться на сам объект в вашем процессе, иначе он не будет собран мусорщиком. (Не подберу что-нибудь живое и здоровое)

class MyObject
  def generate_image
    image = ImageMagick.do_some_magick
    ObjectSpace.define_finalizer(self, proc { image.self_destruct! })
  end
end
11 голосов
/ 15 мая 2013

Причуды GC приятно читать, но почему бы не правильно освободить ресурсы в соответствии с уже существующим языковым синтаксисом?

Позвольте мне уточнить это.

class ImageDoer
  def do_thing(&block)
    image= ImageMagick.open_the_image # creates resource
    begin
      yield image # yield execution to block
    rescue
      # handle exception
    ensure
      image.destruct_sequence # definitely deallocates resource
    end
  end
end

doer= ImageDoer.new
doer.do_thing do |image|
  do_stuff_with_image # destruct sequence called if this throws
end # destruct_sequence called if execution reaches this point

Изображение уничтожается после завершения выполнения блока. Просто начните блок, сделайте всю обработку изображения внутри, затем позвольте изображению уничтожить себя. Это аналогично следующему примеру C ++:

struct Image
{
  Image(){ /* open the image */ }
  void do_thing(){ /* do stuff with image */ }
  ~Image(){ /* destruct sequence */ }
};

int main()
{
  Image img;
  img.do_thing(); // if do_thing throws, img goes out of scope and ~Image() is called
} // special function ~Image() called automatically here
3 голосов
/ 11 мая 2011

В Ruby есть ObjectSpace.define_finalizer для установки финализаторов на объектах, но его использование точно не поощряется и довольно ограничено (например, финализатор не может ссылаться на объект, для которого он установлен, иначе финализатор сделает объект неприемлемым для сбора мусора).

2 голосов
/ 11 мая 2011

В Ruby на самом деле нет такого понятия, как деструктор.

Что вы можете сделать, это просто удалить все файлы, которые больше не открыты, или использовать класс TempFile, который сделает это за вас.

Обновление :

Ранее я утверждал, что PHP, Perl и Python не имеют деструкторов, но, как указывает igorw, это неверно.Я не видел, чтобы они использовались очень часто.Правильно созданный деструктор необходим в любом языке, основанном на распределении, но в мусорном сборнике он оказывается необязательным.

0 голосов
/ 23 июля 2016

Чтобы реализовать нечто похожее на менеджер контекста Python в Ruby:

#!/usr/bin/env ruby

class Customer
   @@number_of_customers = 0

   def initialize(id, name)
      @_id = id
      @_name = name
      @@number_of_customers += 1
   end

   def self.get_number_of_customers()
      return @@number_of_customers
   end

   def get_id()
      return @_id
   end

   def get_name()
      return @_name
   end

   def finalize()
      @@number_of_customers -= 1
   end
end

class Manager
   def self.manage_customer(*custs, &block)
      yield custs
      custs.each do |c|
         c.finalize()
      end
   end
end

Manager.manage_customer(Customer.new(0, 'foo'), Customer.new(1, 'bar')) do |custs|
   puts("id is #{custs[0].get_id()}")
   puts("id is #{custs[1].get_id()}")
   puts("name is #{custs[0].get_name()}")
   puts("name is #{custs[1].get_name()}")
   puts("number of customers is #{Customer.get_number_of_customers()}")
end

puts("number of customers is #{Customer.get_number_of_customers()}")

В итоге, здесь происходит то, что Manager похож на использование Python с ключевым словом. Manager - это класс высокого уровня, который получает объекты Customer от клиента, возвращает их обратно и явно уничтожает их в конце своей области действия, когда клиент завершает их использование (что неявно с точки зрения клиента).

0 голосов
/ 29 ноября 2012

Существует очень простое решение для вашей проблемы. Рубиновый дизайн побуждает вас делать все действия определенным и понятным способом. Нет необходимости в магических действиях в конструкторе / деструкторе. Да, конструкторы требуются как удобный способ назначить начальное состояние объекта, но не для «магических» действий. Позвольте мне проиллюстрировать этот подход на возможном решении. Цель - сохранить объекты изображения доступными, но очистить кэш-файлы изображений.

# you are welcome to keep an in memory copy of the image
# GC will take care of it.
class MyImage
  RawPNG data
end

# this is a worker that does operations on the file in cache directory.
# It knows presizely when the file can be removed (generate_image_final)
# no need to wait for destructor ;)
class MyImageGenerator
  MyImage @img

  def generate_image_step1
    @image_file = ImageLib.create_file
  end
  def generate_image_step2
    ImageLib.draw @image_file
  end
  def generate_image_final
    @img=ImageLib.load_image @image_file
    delete_that_file @image_file
  end

  def getImage
    # optional check image was generated
    return @img
  end
end
...