Ruby:析构函数?

29

我需要偶尔在缓存目录中使用RMagick创建图像。

为了快速摆脱它们,同时不影响视图,我想在我的Ruby Image-Class实例被破坏或进入Garbage Collection时删除图像文件。

我需要重写哪个类方法来向析构函数注入代码?


显式销毁对象 - 这是设计之中的。 - solstice333
6个回答

27

@edgerunner的解决方案几乎可以使用。基本上,您不能创建一个闭包来代替define_finalizer调用,因为它会捕获当前self的绑定。在Ruby 1.8中,似乎您也不能使用从绑定到self的方法转换(使用to_proc)的任何proc对象。要使其起作用,您需要一个不会捕获正在定义finalizer的对象的proc对象。

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

不错的文章。我注意到即使在上述测试的各个点调用GC.start,Ruby 1.8也不会按照人们预期的顺序精确地运行finalize()方法。我从Ruby 1.9.3中得到了可预测的排序。上面的代码似乎工作正常,但我建议不要依赖finalize()将被调用的时间。注意:我使用了你未注释的define_finalizer(...)版本。 - dinman2022
如果不起作用,则会抛出父进程中的异常。 - Малъ Скрылевъ

23
你可以在创建图像文件时使用 ObjectSpace.define_finalizer,垃圾回收器在回收时会自动调用该方法。只需注意不要在处理程序中引用对象本身,否则它将不会被垃圾回收器回收。(不会回收仍然存在的对象)
class MyObject
  def generate_image
    image = ImageMagick.do_some_magick
    ObjectSpace.define_finalizer(self, proc { image.self_destruct! })
  end
end

4
据我所知(我承认我的经验不太丰富),这种方法行不通,因为进程会对它们定义的上下文中的self保持隐式引用——在这种情况下,即与附加终结器的对象相同,因此终结器将防止对象被回收。 - Chuck
可能可以工作,但对我来说并没有按预期运行... 我用一个简单的脚本 http://pastie.org/1892817 来尝试了一下 -- 请看 pastie.. 然后我猜想这个对象可能只是没有被 GC 捕捉到... 但是在退出脚本时,你仍然没有得到预期的输出。 - Joern Akkermann
4
这真的是一个非常重要的特性,但却缺失了,Matz先生,我们需要析构函数! :) - Joern Akkermann

11

GC怪癖很有趣,但为什么不根据已经存在的语言语法正确地释放资源呢?

让我澄清一下。

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

代码块执行完毕后图像将被销毁。只需开始一个块,在其中进行所有的图像处理,然后让图像自行销毁。这类似于以下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

对我来说,这是最好的方法。 - hbobenicio
1
因为使用可能没有词法作用域。实际上,词法作用域的使用只是一个非常小的特殊情况。 - Lothar
@Lothar,我不太理解你的观点,因为即使你在应用程序入口处分配资源,然后在整个代码中重复使用并在该范围内释放这些资源,你仍然可以获得词法作用域。 - nurettin

4

Ruby提供了ObjectSpace.define_finalizer函数来设置对象的终结器,但并不鼓励使用它,而且它的功能比较有限(例如,终结器不能引用被设置终结器的对象,否则该对象将无法进行垃圾回收)。


2

在Ruby中,实际上并不存在析构函数。

你可以简单地清除不再打开的任何文件,或者使用TempFile类来代替你完成这个操作。

更新:

我之前声称PHP、Perl和Python没有析构函数,但是igorw指出这是错误的。虽然我很少见到它们被使用。在任何基于分配的语言中,一个正确构造的析构函数是必不可少的,但在垃圾回收的语言中,它最终变成了可选项。


所以我应该通过每小时扫描它们的日期和时间来删除过时的文件。 - Joern Akkermann
你可以使用一个简单的 shell 命令来删除未使用的文件,例如:find $DIRECTORY -type f -mtime +$RETAIN_PERIOD -exec rm {} \;,其中 $DIRECTORY 是要处理的目录,$RETAIN_PERIOD 是你想要保留它们的天数。你也可以在 Ruby 中使用 File::Stat#mtime 和一些粘合剂来完成这个任务。 - tadman
PHP、Perl和Python都有析构函数,但Ruby却没有。 - igorw
啊,那我就改口了。Perl确实有ENDDESTROY方法,但我从来没有见过普通人使用它们。 - tadman
1
我经常使用Perl的DESTROY方法,因为它在作用域上下文中被调用,类似于C++,非常方便。然而,在Python中,人们会使用上下文管理器来实现类似的效果,因为其析构函数是基于引用计数调用的。而Ruby似乎需要更多的手动操作。 - solstice333
PHP有一个叫做__destruct()的魔术方法,但是它们仍然工作得很糟糕,无论我多么喜欢PHP...此外,$instance = null在PHP中几乎等同于销毁。 - Valentin Rusk

0

针对您的问题,有一个非常简单的解决方案。Ruby的设计鼓励您以明确和清晰的方式执行所有操作。在构造函数/析构函数中不需要进行任何神奇的操作。是的,构造函数是必需的,作为分配对象初始状态的便捷方式,但不是用于“神奇”操作的。让我通过可能的解决方案来说明这种方法。 目标是保持图像对象可用,但清除图像缓存文件。

# 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

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接