Gaucheクックブック

Gauche (ゴーシュ)クックブックは動作する短いコードを一問一答形式で提示していくスタイルのプログラム解説ページです。毎週月曜、木曜に更新。

2007-09-20

Weakベクタを使う

問題

JavaやRubyには、weak referenceといって、指している先のオブジェクトがGCされることを許す参照がある。Gaucheにもそういう機能はあるか。

答え

Gaucheでweak referenceは、weakベクタとして実装されています。Weakベクタからしか参照されていないオブジェクトは回収される可能性があり、参照している先のオブジェクトがGCされた場合、そのエントリには#fがセットされます。

(define wv (make-weak-vector 2))   ; => #,(<weak-vector> 1 #f #f)
(weak-vector-set! wv 0 (cons 1 2)) ; => #,(<weak-vector> 2 (1 . 2) #f)
(gc)                               ; GCを起動
wv                                 ; => #,(<weak-vector> 1 #f #f)

GCの振る舞いには不確かなところがあるので、Weakベクタからしか参照されていないからといって、オブジェクトが必ず回収されるということは期待しないでください。

解説

Weakベクタは、Gauche内部でポートのセットを保持するために使われています。flushは全てのポートをフラッシュすることになっているので、ポートのセットをどこかに持たせておかなければなりませんが、それを普通の(強いリファレンスの)ベクタやリストで保持すると、使用されなくなったポートがGCされないという問題が発生してしまいます。その問題を解決するためにWeakベクタが使われています。

Gaucheの上でプログラミングを行うユーザも、まさに上のポートの話と同じような場面に出会うことがあります。たとえば、インスタンスのセットを自動的に保持するようなクラスを考えてみます。そのようなクラスは次のように、インスタンスの初期化の際にクラススロットにインスタンスを登録していくクラスとして実装できます(Wiliki: Scheme:MOP:InstancePool)。

(define-class <foo> ()
  ...
  (all-instances :allocation :class :init-value ())
  )

(define-method initialize ((self <foo>) initargs)
  (next-method)
  (push! (slot-ref self 'all-instances) self)))

しかし上のクラスでは、インスタンスがGCされなくなってしまいます。そこでweakベクタを使って、インスタンスが回収されることを許すクラスを設計できます。

(define-class <foo> ()
  ((all-instances :allocation :class
                  :init-form (make-weak-vector 8))))

(define-method initialize ((self <foo>) initargs)
  (next-method)
  (let* ((wv (slot-ref self 'all-instances))
         (wvlen (weak-vector-length wv)))
    (let loop ((i 0))
      (cond ((= i wvlen)
             ;; Weakベクタがフルなので拡張する
             (let1 new (make-weak-vector (* 2 wvlen))
               (dotimes (i wvlen)
                 (weak-vector-set! new i (weak-vector-ref wv i)))
               (set! (slot-ref self 'all-instances) new)
               (weak-vector-set! new (+ wvlen 1) self)))
            ;; 空のエントリにインスタンスを登録する
            ((not (weak-vector-ref wv i))
             (weak-vector-set! wv i self))
            (else
             (loop (+ i 1)))))))

(make <foo>)を評価してインスタンスを作成し、(class-slot-ref <foo> 'all-instances)でインスタンスのセットに登録されているのを確認してください。その後(gc)を評価すると、インスタンスのいくつかがセットから削除されているのが確認できるはずです。

なお、WeakベクタのAPIでは、Weakリファレンスが#fで置き換えられたのか、最初から#fがセットされていたのか、区別はできません。区別ができなくてかまわないような用途に利用してください。

参照