不,Ruby 没有 PHP 等价物__destruct
,因为 PHP 会在其引用计数达到零时立即销毁对象,但 Ruby 销毁对象的速度很慢。Ruby 只是不时地标记和扫描对象。此外,Ruby 在扫描 C 代码的局部变量时是保守的。如果 C 本地可能指向某个对象,Ruby 拒绝销毁该对象。
Ruby 有ObjectSpace.define_finalizer
,但很难用。一个对象可以有多个终结器。如果超类定义了终结器,您可以在同一对象上定义另一个终结器。问题是ObjectSpace.undefine_finalizer
从对象中删除所有终结器。因此,如果超类删除了它的终结器,它也会删除你的终结器。
Ruby 仅在销毁对象后运行终结器。终结器不能引用对象。如果是这样,Ruby 永远不会破坏该对象,并且您会发生内存泄漏。终结器不得位于self
或局部变量引用对象的范围内。
在下一个示例中,我将展示如何使用终结器来更新数据库。这是一个坏主意,因为 Ruby 运行终结器的速度很慢。数据库可能很长时间没有更新。我呼吁GC.start
强制进行完整的垃圾收集。我计算机的 Ruby 拒绝销毁其中一个对象,因此还没有发生一次更新。
require 'forwardable'
require 'pstore'
# Create a database of people's ages.
People = PStore.new('people.pstore')
People.transaction do
People['Alice'] = 20
People['Bob'] = 30
People['Carl'] = 40
People['David'] = 50
People['Eve'] = 60
end
# Shows people in database. This can show old values if someone
# forgot to update the database!
def show_people(heading)
People.transaction(true) do
puts heading
%w[Alice Bob Carl David Eve].each do |name|
puts " #{name} is #{People[name]} years old."
end
end
end
show_people("Before birthdays:")
# This is a person in the database. You can change his or her age,
# but the database is only updated when you call #update or by this
# object's finalizer.
class Person
# We need a PersonStruct for the finalizer, because Ruby destroys
# the Person before calling the finalizer.
PersonStruct = Struct.new(:name, :age, :oage)
class PersonStruct
def update(_)
s = self
if s.age != s.oage
People.transaction { People[s.name] = s.oage = s.age }
end
end
end
private_constant :PersonStruct
# Delegate name (r) and age (rw) to the PersonStruct.
extend Forwardable
def_delegators(:@struct, :name, :age, :age=)
# Find a person in the database.
def initialize(name)
age = People.transaction(true) { People[name] }
@struct = PersonStruct.new(name, age, age)
ObjectSpace.define_finalizer(self, @struct.method(:update))
end
# Update this person in the database.
def update
@struct.update(nil)
end
end
# Now give everyone some birthdays.
Person.new('Alice').age += 2
Person.new('Bob').age += 1
Person.new('Carl').age += 1
Person.new('David').age += 1
Person.new('Eve').age += 2
# I forgot to keep references to the Person objects and call
# Person#update. Now I try to run the finalizers.
GC.start
# Did the database get updated?
show_people("After birthdays:")
我的电脑给出了这个输出:
Before birthdays:
Alice is 20 years old.
Bob is 30 years old.
Carl is 40 years old.
David is 50 years old.
Eve is 60 years old.
After birthdays:
Alice is 22 years old.
Bob is 31 years old.
Carl is 41 years old.
David is 51 years old.
Eve is 60 years old.
我在 Eve 的年龄上加了 2 岁,但在我检查数据库之前没有更新。解释 Ruby 的 C 代码可能Person.new('Eve')
在某个局部变量中留下了对的引用,因此 Ruby 不会破坏该对象。如果您使用其他版本的 Ruby 或不同的 C 编译器,结果可能会发生变化。当程序退出时,Ruby 会运行任何剩余的终结器,所以更新确实发生了,但为时已晚。