Ruby: Les Destructeurs?
J'ai besoin de créer occasionnellement des images avec rmagick dans un répertoire de cache.
Pour ensuite s'en débarrasser rapidement, sans les perdre pour la vue, je veux supprimer les fichiers image pendant que mon Instance Ruby de la classe Image est détruite ou entre dans la Garbage Collection.
Quelle ClassMethod dois-je écraser pour alimenter le destructeur avec du code?
7 réponses
Vous pouvez utiliser ObjectSpace.define_finalizer
lorsque vous créez le fichier image, et il ne sera appelée lorsque le garbage man vient de recueillir. Veillez simplement à ne pas référencer l'objet lui-même dans votre proc, sinon il ne sera pas collecté par le garbage man. (Ne va pas ramasser quelque chose qui est vivant et coups de pied)
class MyObject
def generate_image
image = ImageMagick.do_some_magick
ObjectSpace.define_finalizer(self, proc { image.self_destruct! })
end
end
La solution de@edgerunner a presque fonctionné. Fondamentalement, vous ne pouvez pas créer une fermeture à la place de l'appel define_finalizer
car cela capture la liaison du self
actuel. Dans Ruby 1.8, il semble que vous ne pouvez pas utiliser d'objet proc
converti (en utilisant to_proc
) à partir d'une méthode liée à self
non plus. Pour le faire fonctionner, vous avez besoin d'un objet proc
qui ne capture pas l'objet pour lequel vous définissez le finaliseur.
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
Les bizarreries GC sont agréables à lire, mais pourquoi ne pas désallouer correctement les ressources selon la syntaxe de langage déjà existante?
Permettez-moi de clarifier cela.
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
L'Image est détruite après la fin de l'exécution du bloc. Il suffit de commencer un bloc, faire tout le traitement d'image à l'intérieur, puis laissez l'image se détruire. Ceci est analogue à L'exemple c++ suivant:
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 a ObjectSpace.define_finalizer
pour définir les finaliseurs sur les objets, mais son utilisation n'est pas exactement encouragée et elle est plutôt limitée (par exemple, le finaliseur ne peut pas se référer à l'objet pour lequel il est défini ou bien le finaliseur rendra l'objet inéligible pour la récupération de place).
Il n'y a vraiment pas de destructeur dans Ruby.
Ce que vous pouvez faire est simplement d'effacer tous les fichiers qui ne sont plus ouverts, ou d'utiliser la classe TempFile qui le fait pour vous.
Mise à Jour:
J'ai précédemment prétendu que PHP, Perl et Python n'ont pas de destructeurs, mais cela semble être faux comme le souligne igorw. Je ne les ai pas vus utilisés très souvent, cependant. Un destructeur correctement construit est essentiel dans n'importe quel langage basé sur l'allocation, mais dans un garbage collecté un il finit par être facultatif.
Il y a une solution très simple pour votre problème. Ruby design vous encourage à faire toutes les actions de manière définie et claire. Pas besoin d'actions magiques dans constructor / destructor. Oui, les constructeurs sont requis comme un moyen pratique d'attribuer l'état initial de l'objet, mais pas pour les actions "magiques". Permettez-moi d'illustrer cette approche sur une solution possible. Objectif, pour garder les objets d'image disponibles, mais nettoyer les fichiers de cache des 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
Pour implémenter quelque chose de similaire au gestionnaire de contexte de Python dans 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 résumé, ce qui se passe ici est que le gestionnaire est similaire à l'utilisation de Python avec le mot-clé. Manager est une classe de haut niveau qui reçoit des objets client du client, les renvoie et les détruit explicitement à la fin de sa portée lorsque le client a terminé de les utiliser (ce qui est implicite du point de vue du client).