在Ruby中删除对象

时间:2012-02-23 02:43:51

标签: ruby garbage-collection finalizer

假设我有以下课程:

class Vehicle
    @@total_vehicles = 0
    @@all_instances = Array.new

    def initialize
        @@total_vehicles += 1
        @@all_instances << self
    end

    def total_vehicles #returns total number of Vehicles 'alive'
        return @@total_vehicles
    end

    def all_vehicles #returns an array of all Vehicle objects
        return @@all_instances
    end

end

现在,为了使@@total_vehicles@@all_instances保持最新和正确,我想确保在其中一个对象被垃圾回收时,它们分别正确递减和更新。但这是发生的事情:

v = Vehicle.new
Vehicle.total_vehicles # => 1
v = nil #no references to Vehicle instance now
ObjectSpace.garbage_collect #instance garbage collected
Vehicle.total_vehicles # => 1    Nope!

好吧,我可以为Vehicle类的每个实例添加一个终结器Proc,当调用该对象的垃圾收集时,它将被调用。但是根据文档,{/ 1}}会在之后调用someProc 来实现Vehicle实例的销毁 - 这意味着我不能在那里使用ObjectSpace.define_finalizer(v,someProc)self(因为会有没有类,因为没有对象!)我可以让proc在Vehicle类上调用一个公共访问器方法,但这样就消除了类变量只能访问类及其实例的目的 - &gt;基本上将类变量转换为gvars。

如何在进行垃圾回收之前获得相似的析构函数方法(来自C ++),以便按顺序获取Vehicle实例的事务?

P.S。 self.class不是一个可行的选择,因为即使Ruby文档也是关于它的。

2 个答案:

答案 0 :(得分:3)

您几乎肯定想要的是标准库中的WeakRef类。它处理对象跟踪和管理的所有细节,而不会阻止引用计数。

使用指向跟踪对象的WeakRef,您可以将整个完成工作委派给库,并简化您自己的生活。 (您可能需要从数组中刷新死项,但这很容易包装在父类中。)

例如:

def all_instances
   # this will vacuum out the dead references and return the remainder.
   @@weakrefs_to_vehicles = @@weakrefs_to_vehicles.select(&:weakref_alive?)
end

def total_vehicles
   all_instances.count
end

答案 1 :(得分:2)

现在,它们永远不会被垃圾收集,因为你在@@all_instances中持有一个引用。您可以使用终结器来获得所需的结果:

class Vehicle
  class << self
    attr_accessor :count
    def finalize(id)
      @count -= 1
    end

    def all #returns an array of all Vehicle objects
      ObjectSpace.each_object(Vehicle).to_a
    end
  end
  Vehicle.count ||= 0

  def initialize
    Vehicle.count += 1
    ObjectSpace.define_finalizer(self, Vehicle.method(:finalize))
  end
end

100.times{Vehicle.new}
p Vehicle.count  # => 100
ObjectSpace.garbage_collect
p Vehicle.count  # => 1, not sure why
p Vehicle.all    # => [#<Vehicle:0x0000010208e730>]

如果您运行此代码,您将看到它“正常工作”,除了仍有一个非垃圾收集的Vehicle。我不确定为什么会这样。

还可以通过返回count

更简单地定义您的ObjectSpace.each_object(Vehicle).count方法

最后,如果您真的想维护现有的车辆列表,则需要存储其ID并使用ObjectSpace._id2ref

require 'set'

class Vehicle
  class << self
    def finalize(id)
      @ids.delete(id)
    end

    def register(obj)
      @ids ||= Set.new
      @ids << obj.object_id
      ObjectSpace.define_finalizer(obj, method(:finalize))
    end

    def all #returns an array of all Vehicle objects
      @ids.map{|id| ObjectSpace._id2ref(id)}
    end

    def count
      @ids.size
    end
  end

  def initialize
    Vehicle.register(self)
  end
end