RubyのRAII(またはRubyでリソースを管理する方法)
-
03-07-2019 - |
質問
オブジェクトが破壊されたときに何が起こるかを制御できないのは、設計によるものです。また、いくつかのクラスメソッドをファイナライザとして定義することも知っています。
しかし、C ++のRAIIのルビのイディオム(リソースはコンストラクターで初期化され、デストラクタで閉じられます)?エラーや例外が発生した場合でも、オブジェクト内で使用されるリソースをどのように管理しますか?
保証を使用すると動作します:
f = File.open("testfile")
begin
# .. process
rescue
# .. handle error
ensure
f.close unless f.nil?
end
ただし、クラスのユーザーは、 openメソッドを呼び出す必要があるたびに、begin-rescue-ensure chacha全体を実行することを忘れないでください。
たとえば、次のクラスがあります:
class SomeResource
def initialize(connection_string)
@resource_handle = ...some mojo here...
end
def do_something()
begin
@resource_handle.do_that()
...
rescue
...
ensure
end
def close
@resource_handle.close
end
end
例外が他のクラスによって引き起こされ、スクリプトが終了した場合、resource_handleは閉じられません。
それとも私がまだこれをしているのはC ++のような問題ですか?
解決
ユーザーが" begin-rescue-ensure chacha全体を忘れずに行う必要があるように" rescue
/ ensure
と yield
を組み合わせます。
class SomeResource
...
def SomeResource.use(*resource_args)
# create resource
resource = SomeResource.new(*resource_args) # pass args direct to constructor
# export it
yield resource
rescue
# known error processing
...
ensure
# close up when done even if unhandled exception thrown from block
resource.close
end
...
end
クライアントコードでは次のように使用できます。
SomeResource.use(connection_string) do | resource |
resource.do_something
... # whatever else
end
# after this point resource has been .close()d
実際、これが File.open
の動作です-最初の答えはせいぜい混乱させます(それは私の仕事仲間にとってでした)。
File.open("testfile") do |f|
# .. process - may include throwing exceptions
end
# f is guaranteed closed after this point even if exceptions are
# thrown during processing
他のヒント
ブロックへのリソースの yield
はどうですか?例:
File.open("testfile") do |f|
begin
# .. process
rescue
# .. handle error
end
end
それとも私がまだこれをしているのはC ++のような問題ですか?
はい、C ++ではスタック上のすべてのリソースの割り当てが暗黙的に解除されるためです。 Stack unwound =リソースが破棄され=デストラクタが呼び出され、そこから解放されます。 Rubyにはデストラクタがないため、「他のすべてが完了したら、それを行う」ことはできません。グラベージコレクションは現在地から数サイクル遅れる可能性があるためです。ファイナライザーはありますが、「リンボ」と呼ばれます。 (すべてが利用できるわけではありません)、GCで呼び出されます。
したがって、リリースする方がよいリソースへのハンドルを保持している場合は、明示的にリリースする必要があります。実際、この種の状況を処理する正しいイディオムは次のとおりです
def with_shmoo
handle = allocate_shmoo
yield(handle)
ensure
handle.close
end
http://www.rubycentral.com/pickaxe/tut_exceptions.html
Rubyでは、 ensure
ステートメントを使用します。
f = File.open("testfile")
begin
# .. process
rescue
# .. handle error
ensure
f.close unless f.nil?
end
これは、try、catch、finallyのように機能するという点で、Python、Java、またはC#のユーザーになじみがあります。