いいえ、Ruby には PHP__destruct
に相当するものはありません。なぜなら、PHP は参照カウントがゼロになるとすぐにオブジェクトを破棄しますが、Ruby はオブジェクトを破棄するのが遅いからです。Ruby は時々オブジェクトをマークしてスイープします。また、Ruby は C コードのローカル変数をスキャンする際に保守的です。C ローカルがオブジェクトを指している可能性がある場合、Ruby はオブジェクトの破棄を拒否します。
RubyはObjectSpace.define_finalizer
あるけど使いにくい。オブジェクトは複数のファイナライザーを持つことができます。スーパークラスがファイナライザーを定義した場合、同じオブジェクトに別のファイナライザーを定義できます。問題はObjectSpace.undefine_finalizer
、オブジェクトからすべてのファイナライザーを削除することです。したがって、スーパークラスがそのファイナライザーを削除すると、ファイナライザーも削除されます。
Ruby は、オブジェクトを破棄した後にのみファイナライザーを実行します。ファイナライザーには、オブジェクトへの参照があってはなりません。その場合、Ruby はオブジェクトを破棄せず、メモリ リークが発生します。self
ファイナライザーは、またはローカル変数がオブジェクトを参照するスコープ内にあってはなりません。
次の例では、ファイナライザーを使用してデータベースを更新する方法を示します。Ruby はファイナライザーの実行が遅いため、これは悪い考えです。データベースが長時間更新されない可能性があります。GC.start
完全なガベージ コレクションを強制するために呼び出します。私のコンピューターの Ruby はオブジェクトの 1 つを破棄することを拒否するため、1 つの更新はまだ行われていません。
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.
イブの年齢を2歳足しましたが、データベースを確認するまで更新はありませんでした。Ruby を解釈する C コードはPerson.new('Eve')
、一部のローカル変数に参照を残している可能性があるため、Ruby はオブジェクトを破棄しません。別のバージョンの Ruby または別の C コンパイラを使用すると、結果が変わる可能性があります。Ruby はプログラムの終了時に残りのファイナライザーを実行するため、更新は行われましたが、遅すぎました。