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?

22
demandé sur meagar 2011-05-11 00:29:38

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
19
répondu edgerunner 2011-05-10 21:26:30

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
24
répondu Su Zhang 2012-12-20 09:30:42

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
10
répondu nurettin 2017-02-23 06:36:02

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).

3
répondu Chuck 2011-05-10 21:07:29

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.

2
répondu tadman 2011-05-10 21:04:38

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
0
répondu smile-on 2012-11-28 23:14:42

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).

-1
répondu solstice333 2016-07-23 09:26:42