Ruby 中的 RAII(或者,如何在 Ruby 中管理资源)

发布于 2024-07-07 07:10:52 字数 776 浏览 10 评论 0原文

我知道这是设计使然,你无法控制对象被销毁时会发生什么。 我还知道将某些类方法定义为终结器。

然而,C++ 的 RAII 的 ruby​​ 习惯用法是这样的吗(资源在构造函数中初始化,在析构函数中关闭)? 即使发生错误或异常,人们如何管理对象内部使用的资源?

使用ensure有效:

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++了?

I know it's by design that you can't control what happens when an object is destroyed. I am also aware of defining some class method as a finalizer.

However is the ruby idiom for C++'s RAII (Resources are initialized in constructor, closed in destructor)? How do people manage resources used inside objects even when errors or exceptions happen?

Using ensure works:

f = File.open("testfile")
begin
  # .. process
rescue
  # .. handle error
ensure
  f.close unless f.nil?
end

but users of the class have to remember to do the whole begin-rescue-ensure chacha everytime the open method needs to be called.

So for example, I'll have the following class:

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

The resource_handle won't be closed if the exception is cause by some other class and the script exits.

Or is the problem more of I'm still doing this too C++-like?

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

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

发布评论

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

评论(4

鯉魚旗 2024-07-14 07:10:52

这样用户就不必“必须记住执行整个 begin-rescue-ensure chacha”,将 rescue/ensure结合起来产量。

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

So that users don't "have to remember to do the whole begin-rescue-ensure chacha" combine rescue/ensure with 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

Client code can use it as follows:

SomeResource.use(connection_string) do | resource |
  resource.do_something
  ... # whatever else
end
# after this point resource has been .close()d

In fact this is how File.open operates - making the first answer confusing at best (well it was to my work colleagues).

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
与酒说心事 2024-07-14 07:10:52

将资源yield给一个块怎么样? 例子:

File.open("testfile") do |f|
  begin
    # .. process
  rescue
    # .. handle error
  end
end

How about yielding a resource to a block? Example:

File.open("testfile") do |f|
  begin
    # .. process
  rescue
    # .. handle error
  end
end
只想待在家 2024-07-14 07:10:52

或者问题更多的是我仍然这样做太像C++了?

是的,因为在 C++ 中,堆栈上的所有内容都会隐式进行资源释放。 堆栈展开=资源被破坏=调用析构函数,并且可以从那里释放东西。 由于 Ruby 没有析构函数,因此不存在“当其他所有事情都完成时再执行此操作”的地方,因为抓取收集可能会比您所处的位置延迟几个周期。 你确实有终结器,但它们被称为“inlimbo”(并不是所有东西都可用),并且它们在 GC 上被调用。

因此,如果您持有某个最好释放的资源的句柄,则需要显式释放它。 事实上,处理这种情况的正确习惯用法是

def with_shmoo
  handle = allocate_shmoo
  yield(handle)
ensure
  handle.close
end

Or is the problem more of I'm still doing this too C++-like?

Yes it is since in C++ resource deallocation happens implicitly for everything on the stack. Stack unwound = resource destroyed = destructors called and from there things can be released. Since Ruby has no destructors there is no "do that when everything else is done with" place since grabage collection can be delayed several cycles from where you are. You do have finalizers but they are called "in limbo" (not everything is available to them) and they get called on GC.

Therefore if you are holding a handle to some resource that better be released you need to release it explicitly. Indeed the correct idiom to handle this kind of situation is

def with_shmoo
  handle = allocate_shmoo
  yield(handle)
ensure
  handle.close
end
转角预定愛 2024-07-14 07:10:52

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

这对于 Python、Java 或 C# 用户来说很熟悉,因为它的工作方式类似于 try / catch / finally。

See http://www.rubycentral.com/pickaxe/tut_exceptions.html

In Ruby, you would use an ensure statement:

f = File.open("testfile")
begin
  # .. process
rescue
  # .. handle error
ensure
  f.close unless f.nil?
end

This will be familiar to users of Python, Java, or C# in that it works like try / catch / finally.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文