Ruby: Destructors?

2019-01-08 20:31发布

问题:

I need to occasionaly create images with rmagick in a cache dir.

To then get rid of them fast, without loosing them for the view, I want to delete the image-files while my Ruby Instance of the Image-Class get's destructed or enters the Garbage Collection.

What ClassMethod must I overwrite to feed the destructor with code?

回答1:

You can use ObjectSpace.define_finalizer when you create the image file, and it will get invoked when the garbage man comes to collect. Just be careful not to reference the object itself in your proc, otherwise it won't be collected by the garbage man. (Won't pick up something that's alive and kicking)

class MyObject
  def generate_image
    image = ImageMagick.do_some_magick
    ObjectSpace.define_finalizer(self, proc { image.self_destruct! })
  end
end


回答2:

@edgerunner's solution almost worked. Basically, you cannot create a closure in place of the define_finalizer call since that captures the binding of the current self. In Ruby 1.8, it seems that you cannot use any proc object converted (using to_proc) from a method that is bound to self either. To make it work, you need a proc object that doesn't capture the object you are defining the finalizer for.

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


回答3:

GC quirks are nice to read about, but why not properly deallocate resources according to already existing language syntax?

Let me clarify that.

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

Image is destroyed after the block finishes executing. Just start a block, do all the image processing inside, then let the image destroy itself. This is analogous to the following C++ example:

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


回答4:

Ruby has ObjectSpace.define_finalizer to set finalizers on objects, but its use isn't exactly encouraged and it's rather limited (e.g. the finalizer can't refer to the object it is set for or else the finalizer will render the object ineligible for garbage collection).



回答5:

There's really no such thing as a destructor in Ruby.

What you could do is simply clear out any files that are no longer open, or use the TempFile class which does this for you.

Update:

I previously claimed that PHP, Perl and Python do not have destructors, but this does appear to be false as igorw points out. I have not seen them used very often, though. A properly constructed destructor is essential in any allocation-based language, but in a garbage collected one it ends up being optional.



回答6:

There is very simple solution for your problem. Ruby design encourage you to do all actions in definite and clear way. No need for magic actions in constructor/destructor. Yes, constructors are required as a convenient way to assign initial state of object but not for "magic" actions. Let me illustrate this approach on possible solution. Goal, to keep image objects available but clean cache files of images.

# 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


回答7:

To implement something similar to Python's context manager in 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()}")

In summary, what's going on here is that Manager is similar to using Python's with keyword. Manager is a high level class that receives Customer objects from the client, yields them back out, and explicitly destroys them at the end of its scope when the client is done using them (which is implicit from the client's perspective).