Ruby: Destructori? (Programare, Ruby, Destructor)

Joern Akkermann a intrebat.

Am nevoie să creez ocazional imagini cu rmagick într-un dir cache.

Pentru a scăpa rapid de ele, fără a le pierde pentru vizualizare, vreau să șterg fișierele de imagine în timp ce instanța mea Ruby a clasei de imagini este distrusă sau intră în Garbage Collection.

Ce ClassMethod trebuie să suprascriu pentru a introduce codul în destructor?

Comentarii

  • Distrugeți obiectele în mod explicit – așa a fost conceput. –  > Por solstice333.
6 răspunsuri
edgerunner

Puteți folosi ObjectSpace.define_finalizer atunci când creați fișierul imagine, iar acesta va fi invocat atunci când gunoierul vine să colecteze. Doar aveți grijă să nu faceți referire la obiectul în sine în proc-ul dvs., altfel nu va fi colectat de către omul de gunoi. (Nu va ridica ceva care este viu și în viață)

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

Comentarii

  • AFAIK (și recunosc că nu am prea multă experiență în acest sens) acest lucru nu va funcționa, deoarece procs păstrează o referință implicită la obiectul self a contextului în care sunt definite – care în acest caz este același obiect la care este atașat finalizatorul, astfel încât finalizatorul va împiedica obiectul să fie colectat vreodată. –  > Por Chuck.
  • poate funcționa, dar nu funcționează pentru mine așa cum mă așteptam… Am încercat cu un script simplu pastie.org/1892817 — vezi pastie… Apoi am presupus că obiectul nu a fost prins de GC… dar, la închiderea scriptului, tot nu se obține rezultatul așteptat…  > Por Joern Akkermann.
  • aceasta este într-adevăr o caracteristică foarte importantă, care lipsește, domnule Matz, avem nevoie de destructori! 🙂 –  > Por Joern Akkermann.
Su Zhang

Soluția lui @edgerunner aproape că a funcționat. Practic, nu puteți crea o închidere în locul lui define_finalizer call, deoarece aceasta capturează legătura dintre curentul self. În Ruby 1.8, se pare că nu se poate utiliza nici un proc obiect convertit (folosind to_proc) de la o metodă care este legată la self fie. Pentru a face să funcționeze, aveți nevoie de un proc obiect care să nu captureze obiectul pentru care definiți finalizatorul.

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

Comentarii

  • Frumoasă postare. Am observat că, chiar și atunci când se apelează GC.start în diferite puncte în cadrul unui test de mai sus, ruby 1.8 nu execută metoda finalize() exact în secvența la care ne-am putea aștepta. Am obținut o secvență previzibilă de la ruby 1.9.3. Codul de mai sus pare să funcționeze bine, dar aș fi precaut să nu mă bazez pe momentul în care finalize() va fi apelată. Notă: Am folosit versiunea de define_finalizer(…) pe care ați lăsat-o necomentată. –  > Por dinman2022.
  • nu va funcționa, atunci se va arunca o excepție în procesul părinte –  > Por Малъ Скрылевъ.
nurettin

Ciudățeniile GC sunt plăcute de citit, dar de ce să nu dezalocăm corect resursele în conformitate cu sintaxa deja existentă a limbajului?

Permiteți-mi să clarific acest lucru.

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

Imaginea este distrusă după ce blocul se termină de executat. Pur și simplu porniți un bloc, faceți toată procesarea imaginii în interiorul acestuia, apoi lăsați imaginea să se distrugă singură. Acest lucru este analog cu următorul exemplu 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

Comentarii

  • Pentru mine, aceasta este cea mai bună abordare. –  > Por hbobenicio.
Chuck

Ruby are ObjectSpace.define_finalizer pentru a seta finalizatori pe obiecte, dar utilizarea sa nu este tocmai încurajată și este destul de limitată (de exemplu, finalizatorul nu se poate referi la obiectul pentru care este setat, altfel finalizatorul va face obiectul neeligibil pentru colectarea gunoiului).

tadman

Nu există cu adevărat un astfel de lucru ca un destructor în Ruby.

Ceea ce ați putea face este să ștergeți pur și simplu toate fișierele care nu mai sunt deschise sau să folosiți clasa TempFile care face acest lucru pentru dumneavoastră.

Actualizare:

Am afirmat anterior că PHP, Perl și Python nu au destructori, dar se pare că acest lucru este fals, după cum subliniază igorw. Totuși, nu le-am văzut folosite foarte des. Un destructor construit corespunzător este esențial în orice limbaj bazat pe alocare, dar într-un limbaj cu colectare de gunoi ajunge să fie opțional.

Comentarii

  • deci ar trebui să șterg fișierele învechite prin scanarea din oră în oră a datei și orei lor –  > Por Joern Akkermann.
  • Puteți omorî fișierele nefolosite cu o simplă comandă de shell de genul: find $DIRECTORY -type f -mtime +$RETAIN_PERIOD -exec rm {} ; where $DIRECTORY este directorul în cauză și $RETAIN_PERIOD este cât timp în zile doriți să le păstrați. Puteți face acest lucru și în Ruby cu File::Stat#mtime și ceva lipici. –  > Por tadman.
  • PHP, Perl și Python au toți destructori. Ruby însă nu are. –  > Por igorw.
  • Ah, am corectat atunci. Perl are END și DESTROY dar nu le-am văzut niciodată folosite de oamenii obișnuiți. –  > Por tadman.
  • Folosesc destul de des metoda DESTROY din Perl și este convenabilă prin faptul că este apelată în contextul domeniului de aplicare similar cu C++. Pentru Python, totuși, s-ar folosi manageri de context pentru a obține un efect similar, deoarece destructorul său este apelat pe baza numărării referințelor. Cu toate acestea, Ruby poate fi un pic mai manual, se pare. –  > Por solstice333.
zâmbește pe

Există o soluție foarte simplă pentru problema dumneavoastră. Ruby design vă încurajează să faceți toate acțiunile într-un mod clar și precis. Nu este nevoie de acțiuni magice în constructor/distructor. Da, constructorii sunt necesari ca o modalitate convenabilă de a atribui starea inițială a obiectului, dar nu pentru acțiuni „magice”. Permiteți-mi să ilustrez această abordare cu privire la o posibilă soluție. Scopul este de a păstra obiectele imagine disponibile, dar de a curăța fișierele cache ale imaginilor.

# 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