【问题标题】:RAII in Ruby (Or, How to Manage Resources in Ruby)Ruby 中的 RAII(或者,如何在 Ruby 中管理资源)
【发布时间】:2010-09-17 21:31:22
【问题描述】:

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

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

使用确保有效:

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

如果异常是由其他类引起的并且脚本退出,则不会关闭资源句柄。

还是更多的问题是我仍然在做类似 C++ 的事情?

【问题讨论】:

    标签: ruby resources destructor raii finalizer


    【解决方案1】:

    这样用户就不必“必须记住完成整个 begin-rescue-ensure chacha”将 rescue/ensureyield 结合起来。

    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
    

    【讨论】:

      【解决方案2】:

      yield将资源分配给块怎么样?示例:

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

      【讨论】:

      • 事实上,使用块调用的 File#open 有一个“确保”块,无论发生什么,它都会正确关闭文件。所以是的,你可以有救援/确保块,但它们是可选的。
      • 没错。使用此习惯用法,您只需为每种类型的资源实现一次 ensure 块,而不是在使用该资源的所有代码中。
      【解决方案3】:

      或者问题更多的是我仍然在做这件事太像 C++?

      是的,因为在 C++ 中,资源释放隐含地发生在堆栈上的所有内容上。堆栈展开 = 资源销毁 = 调用析构函数,然后可以从那里释放东西。由于 Ruby 没有析构函数,因此没有“在其他所有事情都完成后做”的地方,因为抓取收集可以从你所在的位置延迟几个周期。您确实有终结器,但它们被称为“处于边缘”(并非所有东西都可用)并且它们在 GC 上被调用。

      因此,如果您持有一些可以更好地释放的资源的句柄,则需要显式释放它。确实,处理这种情况的正确成语是

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

      【讨论】:

        【解决方案4】:

        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。

        【讨论】:

        • 虽然这行得通,但您必须依靠使用您的班级的人来做正确的事情。他们必须记住每次需要调用 open 方法时都要执行整个 begin-rescue-ensure chacha。我正在编辑问题以使其更清楚,但感谢您的回答:)
        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2017-01-03
        • 1970-01-01
        • 2011-03-18
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多