2010-01-19 61 views
7

无论如何实施资源获取是在计划初始化?RAII在计划?

我知道RAII在GC-ed语言中工作不正常(因为我们不知道该对象是否被销毁)。然而,Scheme具有诸如continuation,dynamic-wind和closure这样的好东西 - 有没有一种方法可以使用这种结合来实现RAII?

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

[一个常见的例子我碰上如下:

我有一个3D网格,我有一个顶点缓存对象atached到它, 当不再使用的网格,我希望VBO腾出。]

谢谢!

+0

嗨,anon。我想知道我的答案是否令你满意,或者你是否在寻找别的东西。 – 2010-01-21 21:01:46

+0

我认为你的回答和它的计划一样好。 我们在某个层面上,我们必须知道模型何时“死亡”,并放弃它的vbo。然而,在RAII + GC中,我不需要事先知道这一点,我们可以说“模型,我不知道你什么时候会死,但是我知道当你这样做时,你会放弃VBO ”。 因为方案是gc-ed,我们不能做得太晚;我最初希望得到的是一些聪明的宏马克,它自动地交错了一些类型的ref-counting,这将提供这种类型的RAII + Refcounting。 – anon 2010-01-22 02:34:45

+0

为了进一步补充,请考虑以下情况:我们创建一个模型,我们不知道它何时被删除,但我们知道它已被渲染很多;所以我们给它一个VBO;通过它很多; ...当没有人使用它时,它释放了VBO。代码中没有一个地方我知道“我现在可以释放模型”。 – anon 2010-01-22 02:35:28

回答

14

如果这仅仅是一次性的,你总是可以只写环绕dynamic-wind,在做之前和之后的thunk的安装和拆卸(我假设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 ... 
) 

下面是一个演示它正在工作的示例,包括使用continuation退出和重新输入示波器(如果控制流程有点难以遵循,这是一个颇为人为的示例,道歉):

(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"))) 

这应该打印:

 
entering foo 
entering bar 
inside 
foo: 1 
bar: 2 
exiting bar 
exiting foo 
* Let's try that again! 
entering foo 
entering bar 
exiting bar 
exiting foo 
* All done 

call/cc仅仅是call-with-current-continuation的缩写;如果您的计划没有较短的计划,请使用较长的表格。

更新:正如您在您的评论中澄清的那样,您正在寻找一种管理可从特定动态环境中返回的资源的方法。在这种情况下,你将不得不使用终结器;终结器是一个函数,一旦GC证明无法从其他地方到达,函数将与您一起调用。终结者不是标准的,但大多数成熟的计划系统有他们,有时名称不同。例如,在PLT计划中,见Wills and Executors

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

泰勒坎贝尔(是的,有一个关系)有an article in his blag(2009-03-28条目)解决这个问题,并提出几个替代方案基于你想要的确切语义。例如,他提供了一个unwind-protext表单,它将不会调用清理过程,直到不再可能重新输入资源可访问的动态上下文为止。

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