在 Ruby 中删除对象

发布于 2025-01-08 10:38:56 字数 1212 浏览 2 评论 0原文

假设我有以下课程:

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,当调用对象的垃圾回收时,将调用该终结器 Proc。但根据文档,ObjectSpace.define_finalizer(v,someProc) 会在 Vehicle 实例被销毁后调用 someProc - 这意味着我无法使用 self或 self.class 在那里(因为不会有类,因为没有对象!)我可以让 proc 调用 Vehicle 类上的公共访问器方法,但这剥夺了类变量只能由类访问,并且它的实例 ->本质上是将类变量转换为 gvar。

我如何才能拥有相当于析构函数方法(来自 C++)的功能,该方法可以在垃圾收集之前按顺序获取 Vehicle 实例的事务?

聚苯乙烯 ObjectSpace#count_objects 不是一个可行的选择,因为甚至 Ruby 文档都在前面。

Let's say I have the following class:

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

Now to keep @@total_vehicles and @@all_instances up-to-date and correct, I want to make sure that they are correctly decremented and updated, respectively, when one of those objects is garbage collected. But here is what happens:

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!

Well I could add a finalizer Proc to each instance of the Vehicle class that, when called upon the object's garbage collection, would be called. But according to the documentation, ObjectSpace.define_finalizer(v,someProc) would call someProc after the Vehicle instance is destroyed - meaning I cannot use self or self.class in there (since there would be no class, as there is no object!) I could have the proc call a public accessor method on the Vehicle class, but that takes away the purpose of class variables being accessible only to the class and its instances -> essentially turning the class variables into gvars.

How can I have the equivalent of a destructor method (from C++) that will get a Vehicle instance's affairs in order, as it were, before getting garbage-collected?

P.S.
ObjectSpace#count_objects is no a viable option, as even the Ruby docs are up front about.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(2

偏爱自由 2025-01-15 10:38:57

您几乎肯定想要的是 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

What you almost certainly want here is the WeakRef class from the standard library. That handles all the details of object tracking and management without blocking reference counting.

Using a WeakRef that points to the object in your tracking you can delegate the whole finalization work to the library, and simplify your own life. (You may need to flush dead items from the arrays, but that is easily enough wrapped in your parent class.)

eg:

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
始于初秋 2025-01-15 10:38:57

现在,它们永远不会被垃圾收集,因为您在 @@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>]

如果您运行此代码,您将看到它“有效”,只是仍然有一辆车辆未进行垃圾收集。我不知道为什么会这样。

您的 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

Right now, they will never be garbage collected, as you are holding a reference in @@all_instances. You could use a finalizer to get the result you want:

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>]

If you run this code, you will see that it "works", except that there remains one Vehicle that is not garbage collected. I'm not sure why that is.

Your count method could be also defined more simply by returning ObjectSpace.each_object(Vehicle).count

Finally, if you really want to maintain a list of existing Vehicles, you need to store their ID and use 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
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文