RAII в Ruby (или, как управлять ресурсами в Ruby) - PullRequest
9 голосов
/ 18 октября 2008

Я знаю, что по замыслу вы не можете контролировать, что происходит, когда объект разрушается. Мне также известно определение некоторого метода класса в качестве финализатора.

Однако является ли идиома ruby ​​для CII RAII (ресурсы инициализируются в конструкторе, закрываются в деструкторе)? Как люди управляют ресурсами, используемыми внутри объектов, даже когда случаются ошибки или исключения?

Использование обеспечивает работает:

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

но пользователи класса должны помнить, что каждый раз, когда требуется вызвать метод open, нужно выполнять 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 ++ - как?

Ответы [ 4 ]

15 голосов
/ 03 июля 2009

Чтобы пользователи не « должны были помнить, что нужно выполнить весь цикл 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
8 голосов
/ 18 октября 2008

Как насчет yield выделения ресурса в блок? Пример:

File.open("testfile") do |f|
  begin
    # .. process
  rescue
    # .. handle error
  end
end
3 голосов
/ 30 ноября 2010

Или проблема в том, что я все еще делаю это тоже на C ++ - как?

Да, так как в C ++ освобождение ресурсов происходит неявно для всего в стеке. Стек размотан = ресурс уничтожен = вызваны деструкторы, и оттуда все можно освободить. Поскольку в Ruby нет деструкторов, нет места «сделай это, когда все остальное будет сделано», поскольку сбор мусора может быть отложен на несколько циклов от того места, где ты находишься. У вас есть финализаторы, но они называются «в подвешенном состоянии» (им не все доступно), и их вызывают в GC.

Поэтому, если вы держите дескриптор какого-либо ресурса, который лучше освободить, вам необходимо явно его освободить. Действительно, правильная идиома, чтобы справиться с такой ситуацией -

def with_shmoo
  handle = allocate_shmoo
  yield(handle)
ensure
  handle.close
end
0 голосов
/ 18 октября 2008

См. 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.

...