make-instances-obsoleteは何もしてないのでは?
Common Lispの、sbclとcclだけの話になります。
ジェネリック関数make-instances-obsolete
とは、
クラスの再定義に関係するものです。
こいつがよく分からない。
たぶん、クラスを再定義したあとで、
すでに作成されているインスタンスを一斉に更新するときに使うのだと思います。
なんで「思います」なのかというと、更新されているように見えないからです。
まずは簡単なクラス再定義の例を示します。
;; クラス作成 (defclass aaa () ((bbb :initarg :bbb))) ;; インスタンスの作成 (defparameter instance (make-instance 'aaa :bbb 100)) ;; インスタンスの更新手順 ;; bbbの値をcccにコピー (defmethod update-instance-for-redefined-class :before ((instance aaa) add del prop &rest initargs) (declare (ignore add del initargs)) (setf (slot-value instance 'ccc) (getf prop 'bbb))) ;; クラスの再定義 (defclass aaa () ((ccc :initarg :ccc))) ;; インスタンスにアクセス (format t "~S~%" (slot-value instance 'ccc)) -> 100
さて、ここで問題にしたいのは、instance
の更新が一体いつ行われるかです。
規約ではdefclass
による再定義からinstance
の読み書きが行われるまで、
いつでも良いことになっています。
でもタイミングが分からないというのは不安なので、
早々に更新してしまいたいと思うかもしれません。
そんなときに使うのがmake-instances-obsolete
関数です。
この関数を用いることで、好きなタイミングで全てのインスタンスを
更新できるはずです。
実際には更新されないんですけどね。
実行例を示します。
;; クラス作成 (defclass aaa () ((bbb :initarg :bbb))) ;; インスタンスの作成 (defparameter instance (make-instance 'aaa :bbb 100)) ;; bbbの値をcccにコピー (defmethod update-instance-for-redefined-class :before ((instance aaa) add del prop &rest initargs) (declare (ignore add del initargs)) (setf (slot-value instance 'ccc) (getf prop 'bbb))) ;; クラスの再定義 (defclass aaa () ((ccc :initarg :ccc))) ;; ★早々に更新してしまおう (make-instances-obsolete 'aaa) ;; インスタンスにアクセス (format t "~S~%" (slot-value instance 'ccc)) -> 100
一見するとうまく行っています。
それではmake-instances-obsolete
の実行で
本当に更新されたのかどうかを確認します。
確認の方法はupdate-instance-for-redefined-class
の中と
更新されるはずの所にformat
文を入れましょう。
(defclass aaa () ((bbb :initarg :bbb))) (defparameter instance (make-instance 'aaa :bbb 100)) (defmethod update-instance-for-redefined-class :before ((instance aaa) add del prop &rest initargs) (declare (ignore add del initargs)) (format t "~&<<update-instance-for-redefined-class>>~%") ;; ★確認 (setf (slot-value instance 'ccc) (getf prop 'bbb))) (defclass aaa () ((ccc :initarg :ccc))) (make-instances-obsolete 'aaa) (format t "Hello~%") ;; ★確認 (format t "~S~%" (slot-value instance 'ccc))
実行結果は次の通り。
Hello <<update-instance-for-redefined-class>> 100
うん、ダメですね。
Hello
の出力の後に更新されています。
本来であれば、update-instance-for-redefined-class
関数は
make-instances-obsolete
で実行されるべきなのですが、
どう見てもslot-value
関数の中で実行されています。
もしかしてやり方が間違っているのかな。
次の方法でも無理でした。
(make-instances-obsolete (find-class 'aaa))
色々やってみたのですが、更新されている様子は無かったです。
なぜmake-instances-obsolete
は何もしないのでしょうか?
処理系を実装しようとしてみると分かるのですが、
変更される前のクラスが生成したインスタンスを全て更新する方法は、
たぶんメモリの全走査しかないと思います。
つまりgerbage collectionと同じようなことをしなければなりません。
そこまでする必要があるかどうかの微妙なラインです。
make-instance
を実行するたびにバッファか何かに
全てのインスタンスを保存しておけばいいのでは?
と考えるかもしれませんが、インスタンスって数が非常に多いので
あまり現実的ではないのだと思います。
という訳でmake-instances-obsolete
は実現不可ということにしましょう。
defclass
による再定義を何度も実行したいから、
make-instances-obsolete
とupdate-instance-for-redefined-class
を
繰り返し実行しようぜ! とか思わない方がいいです。
ただ、make-instances-obsolete
関数が何もしないからと言って、
関数が実行されていない訳ではありません。
クラス再定義の時に実行はされているようです。
次に例を示します。
(defclass aaa () ((bbb :initarg :bbb))) ;; 一つでもインスタンスを作成しないとfinalizeが行われないため ;; make-instances-obsoleteは起動しない (make-instance 'aaa) ;; ★良くない確認方法 (defmethod make-instances-obsolete ((inst standard-class)) (declare (ignore inst)) (format t "~&<<make-instances-obsolete>>~%")) (format t "Hello~%") (defclass aaa () ((ccc :initarg :ccc)))
実行結果は下記の通り
Hello <<make-instances-obsolete>>
上記の例はmake-instances-obsolete
の標準の動作を上書きしてしまうので、
例え何もしないとしても良い実装とは言えませんね。
まとめると次のような結論が得られるのかと思います。
- クラス再定義時に
make-instances-obsolete
は起動される - でも
make-instances-obsolete
は何もしない - インスタンスの更新時期はアクセスされたときのみ
アクセスされたときとは、具体的には下記の四つの関数のどれかが 実行されたときだと考えていいと思います。
slot-value
slot-exists-p
slot-boundp
slot-makunbound
いや、他にもアクセス関数はあるでしょ?
例えばwith-slots
とか、と思うかもしれませんし、
それは正しいのですが、たぶん他の機能は結局上記の
4つの関数に帰着するのかと思います。