无论如何,实施资源获取是否在计划中初始化?
我知道RAII在GC语言中不能很好地工作(因为我们不知道对象是否被破坏).但是,Scheme有很好的东西,比如continuation,dynamic-wind和closures - 有没有办法使用它的一些组合来实现RAII?
如果没有,那么阴谋家如何设计他们的代码而不使用RAII?
[我遇到的一个常见例子如下:
我有一个3D网格,我有一个顶点缓冲对象,当不再使用Mesh时,我想要释放VBO.
谢谢!
如果这仅仅是一次性的,你总是可以只写环绕宏dynamic-wind
,在做之前和之后的thunk的安装和拆卸(我假设allocate-vertex-buffer-object
,并free-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证明无法从其他任何地方到达,它将与您一起调用.终结器不是标准的,但大多数成熟的Scheme系统都有它们,有时名称不同.例如,在PLT Scheme中,请参阅Wills和Executors.
您应该记住,在Scheme中,可以重新输入动态上下文; 这与大多数其他语言不同,在这些语言中,您可以使用异常在任意点退出动态上下文,但不能重新输入.在上面的示例中,我演示了一种天真的方法,dynamic-wind
即在离开动态上下文时使用释放资源,并在再次输入时重新分配它们.这可能适用于某些资源,但是对于许多资源来说它是不合适的(例如,重新打开文件,当您重新进入动态上下文时,现在将位于文件的开头),并且可能具有显着的开销.
Taylor Campbell(是的,有一种关系)在他的blag(2009-03-28条目)中有一篇文章解决了这个问题,并根据你想要的确切语义提出了一些选择.例如,他提供的unwind-protext
表单在不再可能重新进入资源可访问的动态上下文之前不会调用清理过程.
因此,它涵盖了许多可用的不同选项.与RAII没有完全匹配,因为Scheme是一种非常不同的语言,并且具有非常不同的约束.如果您有更具体的用例,或者您简要提到的用例的更多详细信息,我可以为您提供一些更具体的建议.