Ruby: ¿Destructores?

Necesito ocasionalmente crear imágenes con rmagick en un directorio de caché.

Para luego deshacerme de ellos rápidamente, sin perderlos para la vista, quiero eliminar los archivos de imagen mientras mi instancia de Ruby de la clase de imagen se destruye o entra en la recolección de basura.

¿Qué método de clase debo sobrescribir para alimentar el destructor con código?

Puede usar ObjectSpace.define_finalizer cuando crea el archivo de imagen, y se invocará cuando el recolector de basura ObjectSpace.define_finalizer recolección. Solo tenga cuidado de no hacer referencia al objeto en su proc, de lo contrario no será recogido por el basurero. (No recogerá algo que está vivo y pateando)

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

La solución de @ Edgerunner casi funcionó. Básicamente, no puede crear un cierre en lugar de la llamada a define_finalizer ya que captura el enlace del self actual. En Ruby 1.8, parece que no se puede usar ningún objeto de proceso convertido (usando to_proc ) de un método que esté vinculado a self . Para que funcione, necesita un objeto de proceso que no capture el objeto para el que está definiendo el finalizador.

 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 

Las peculiaridades de GC son agradables de leer, pero ¿por qué no desasignar correctamente los recursos de acuerdo con la syntax del lenguaje ya existente?

Déjame aclarar eso.

 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 

La imagen se destruye después de que el bloque termina de ejecutarse. Simplemente comience un bloque, haga todo el procesamiento de la imagen en el interior, luego deje que la imagen se destruya a sí misma. Esto es análogo al siguiente ejemplo de 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 

Ruby tiene ObjectSpace.define_finalizer para establecer finalizadores en los objetos, pero su uso no es exactamente recomendable y es bastante limitado (por ejemplo, el finalizador no puede referirse al objeto para el que está establecido o el finalizador lo hará inelegible para la recolección de basura) )

Realmente no existe un destructor en Ruby.

Lo que podría hacer es simplemente borrar los archivos que ya no están abiertos, o usar la clase TempFile que hace esto por usted.

Actualización :

Anteriormente afirmé que PHP, Perl y Python no tienen destructores, pero esto parece ser falso como señala igorw. Aunque no los he visto muy a menudo. Un destructor construido correctamente es esencial en cualquier lenguaje basado en asignación, pero en un contenedor de basura termina siendo opcional.

Hay una solución muy simple para su problema. El diseño Ruby lo alienta a hacer todas las acciones de una manera definida y clara. No hay necesidad de acciones mágicas en constructor / destructor. Sí, se requieren constructores como una forma conveniente de asignar el estado inicial del objeto pero no para acciones “mágicas”. Permítanme ilustrar este enfoque sobre la posible solución. Objetivo: mantener los objetos de imagen disponibles, pero limpiar los archivos de caché de las imágenes.

 # 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 

Para implementar algo similar al administrador de contexto de Python en 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()}") 

En resumen, lo que sucede aquí es que Manager es similar al uso de Python con la palabra clave. El administrador es una clase de alto nivel que recibe los objetos del cliente del cliente, los devuelve y los destruye explícitamente al final de su scope cuando el cliente termina de usarlos (lo cual está implícito desde la perspectiva del cliente).