2010-01-19 57 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是一種非常不同的語言,並且具有非常不同的限制。如果您有更具體的用例,或者您簡要提及的用例的更多詳細信息,我可以爲您提供一些更具體的建議。