8

无论如何要实现资源获取是Scheme中的初始化?

我知道 RAII 在 GC-ed 语言中效果不佳(因为我们不知道对象何时被销毁)。然而,Scheme 有一些不错的东西,比如延续、动态风和闭包——有没有办法使用这些组合来实现 RAII?

如果不是,计划者如何设计他们的代码以不使用 RAII?

[我遇到的一个常见示例如下:

我有一个 3D 网格,我有一个顶点缓冲区对象附加到它,当不再使用网格时,我希望释放 VBO。]

谢谢!

4

1 回答 1

15

如果这只是一次性的,你总是可以只写一个环绕 的宏,在 thunk 之前和之后进行设置和dynamic-wind拆卸(我假设它是你的构造函数和析构函数):allocate-vertex-buffer-objectfree-vertex-buffer-object

(define-syntax with-vertex-buffer-object
  (syntax-rules ()
    ((_ (name arg ...) body ...)
     (let ((name #f))
       (dynamic-wind
         (lambda () (set! name (allocate-vertex-buffer-object args ...)))
         (lambda () body ...)
         (lambda () (free-vertex-buffer-object name) (set! name #f)))))))

如果这是您经常使用的模式,对于不同类型的对象,您可能会编写一个宏来生成这种宏;并且您可能希望一次分配一系列这些,因此您可能希望在开始时有一个绑定列表,而不仅仅是一个。

这是一个现成的、更通用的版本;我不太确定这个名字,但它展示了基本思想(编辑以修复原始版本中的无限循环):

(define-syntax with-managed-objects
  (syntax-rules ()
    ((_ ((name constructor destructor)) body ...)
     (let ((name #f))
       (dynamic-wind
         (lambda () (set! name constructor))
         (lambda () body ...)
         (lambda () destructor (set! name #f)))))
    ((_ ((name constructor destructor) rest ...)
      body ...)
     (with-managed-objects ((name constructor destructor))
       (with-managed-objects (rest ...)
         body ...)))
    ((_ () body ...)
     (begin body ...))))

您将按如下方式使用它:

(with-managed-objects ((vbo (allocate-vertex-buffer-object 1 2 3)
                            (free-vertext-buffer-object vbo))
                       (frob (create-frobnozzle 'foo 'bar)
                             (destroy-frobnozzle frob)))
  ;; do stuff ...
  )

这是一个演示它工作的示例,包括使用延续退出和重新进入范围(这是一个相当人为的示例,如果控制流有点难以遵循,请道歉):

(let ((inner-continuation #f))
  (if (with-managed-objects ((foo (begin (display "entering foo\n") 1) 
                                  (display "exiting foo\n")) 
                             (bar (begin (display "entering bar\n") (+ foo 1)) 
                                  (display "exiting bar\n")))
        (display "inside\n")
        (display "foo: ") (display foo) (newline)
        (display "bar: ") (display bar) (newline)
        (call/cc (lambda (inside) (set! inner-continuation inside) #t)))
    (begin (display "* Let's try that again!\n") 
           (inner-continuation #f))
    (display "* All done\n")))

这应该打印:

进入 foo
进入酒吧
里面
富:1
酒吧:2
退出栏
退出 foo
* 让我们再试一次!
进入 foo
进入酒吧
退出栏
退出 foo
* 全部做完

call/cc只是call-with-current-continuation;的缩写 如果您的方案没有较短的形式,请使用较长的形式。

更新:正如您在评论中阐明的那样,您正在寻找一种方法来管理可以从特定动态上下文中返回的资源。在这种情况下,您将不得不使用终结器;终结器是一个函数,一旦 GC 证明无法从其他任何地方访问它,它将与您的对象一起调用。终结器不是标准的,但大多数成熟的 Scheme 系统都有它们,有时名称不同。例如,在 PLT Scheme 中,请参阅Wills and Executors

您应该记住,在 Scheme 中,可以重新输入动态上下文;这与大多数其他语言不同,在这些语言中,您可以使用异常在任意点退出动态上下文,但不能重新进入。在上面的示例中,我演示了一种简单的方法,dynamic-wind即在您离开动态上下文时使用释放资源,并在您再次进入时重新分配它们。这可能适用于某些资源,但不适用于许多资源(例如,重新打开一个文件,当您重新进入动态上下文时,您现在将位于文件的开头),并且可能有重大开销。

Taylor Campbell(是的,有关系)在他的博客(2009-03-28 条目)中有一篇文章解决了这个问题,并根据您想要的确切语义提出了一些替代方案。例如,他提供了一个unwind-protext表单,该表单在不再可能重新进入资源可访问的动态上下文之前不会调用清理过程。

因此,这涵盖了许多可用的不同选项。与 RAII 没有完全匹配,因为 Scheme 是一种非常不同的语言并且具有非常不同的约束。如果您有更具体的用例,或者您简要提到的用例的更多详细信息,我也许可以为您提供一些更具体的建议。

于 2010-01-19T02:58:24.353 回答