| [ < ] | [ > ] | [ << ] | [ Up ] | [ >> ] | [Top] | [Contents] | [Index] | [ ? ] |
この節では、インスタンスの生成のしかたと使い方について説明します。
| 7.4.1 インスタンスの作成 | ||
| 7.4.2 インスタンスへのアクセス | ||
| 7.4.3 クラスの変更 |
| [ < ] | [ > ] | [ << ] | [ Up ] | [ >> ] | [Top] | [Contents] | [Index] | [ ? ] |
クラスオブジェクトをつかうと、ジェネリック関数 make でその
クラスのインスタンスを生成できます。標準の <class> に対して
特定化されたメソッドは以下のとおり定義されています。
class のインスタンスを生成し、それを返します。arg … は 典型的な場合には、そのインスタンスを初期化するためのキーワード値のリストです。
概念としては、デフォルトの make メソッドは以下のように定義されて
います。
(define-method make ((class <class>) . initargs)
(let ((obj (allocate-instance class initargs)))
(initialize obj initargs)
obj))
|
すなわち、最初、class のインスタンス用にメモリをアロケートし、
それから、initialize メソッドを用いてそれを初期化します。
classの新規にアロケートされた、初期化されていないインスタンスを返します。
<object> に対するデフォルトの初期化メソッドは以下のように働きます。
デフォルトのスロットアロケーションクラスのなかで、インスタンスアロケート
スロットだけが初期化可能で、上の流れで処理されます。クラスアロケート
スロット(すなわち、スロットアロケーションが :class あるいは
:each-subclass のどちらかの場合)は、:init-value あるいは
:init-form スロットオプションが与えられていれば、クラスオブジェクト
生成時に初期化されます。仮想スロットが、初期化されることはありません。
ユーザ定義アロケーションクラスは、初期化可能にも不可能にも設定することが できます。詳しくは メタオブジェクトプロトコル を参照してください。
initialize メソッドを特定化する場合には next-method が
確実に呼ばれるようにして、新しく生成されたインスタンスのすべての
スロットにアクセスする前にデフォルトの流れで、そのスロットが正しく
初期化されるようにしてください。
定義したクラスに対応する initialize メソッドを特定化して
インスタンスの初期化の方法をカスタマイズするというのが、典型的な
やりかたです。
allocate-instance メソッドを特定化するというのは一般的な方法では
ありません。しかしながら、make がどのように働くかを知っているなら、
make そのものを特定化して、なんらかの状況(たとえば、あらかじめ
アロケートしてあるインスタンスを使うというような状況)でインスタンスの
アロケーションを回避できます。
| [ < ] | [ > ] | [ << ] | [ Up ] | [ >> ] | [Top] | [Contents] | [Index] | [ ? ] |
オブジェクトobjのスロットslotの値を返します。
指定したスロットが値に束縛されていない場合、ジェネリック関数
slot-unboundが3つの引数、objのクラス、obj、slot
を伴って呼び出されます。slot-unboundのデフォルトの振る舞いは、
エラーの通知です。
オブジェクトが指定されたスロットを持っていない場合は、ジェネリック関数
slot-missingが3つの引数、objのクラス、obj、slotを
伴って呼び出されます。slot-missingのデフォルトの振る舞いは、
エラーの通知です。
オブジェクトobjのスロットslotの値を、valueに セットします。
オブジェクトが指定したスロットを持っていない場合は、ジェネリック関数
slot-missingが4つの引数、objのクラス、obj、
slot、valueを伴って呼び出されます。
オブジェクトobjのスロットslotが束縛されていれば真を、 そうでなければ偽を返します。
オブジェクトが指定したスロットを持っていない場合は、ジェネリック関数
slot-missingが3つの引数、objのクラス、obj、
slotを伴って呼び出されます。
objがslotを持っていれば真を返します。
この関数は、一般的なイディオムの実装です。 これは以下のようなコードで定義できます(が、将来のバージョンでは 最適化されるでしょう)。
(define (slot-push! obj slot value) (slot-set! obj slot (cons value (slot-ref obj slot)))) |
これらのメソッドはそれぞれ、単に slot-ref および slot-set!
を呼ぶだけです。直接 slot-ref や slot-set! を呼ぶよりも
効率はすこし悪いですが、プログラムコードはコンパクトになります。
このジェネリック関数は束縛されていないスロットの値を取り出そうとしたときに 呼び出されます。このジェネリック関数の返り値は値を得ようとした呼出しもとに 返されます。
デフォルトのメソッドは単にエラーのシグナルをあげるだけです。
このジェネリック関数は存在しないスロットの値を取り出そうとしたとき、あるいは 設定しようとしたときに呼びだされます。このジェネリック関数の返り値は、 値を得ようとした呼出しもとに返されます。
デフォルトのメソッドは単にエラーのシグナルをあげるだけです。
スロットの:allocationオプションが:classもしくは
:each-subclassである場合、これらの手続きを使って、
インスタンス無しでそれらのスロットの値を取得/設定できます。
slot-ref、slot-set!、slot-bound? のジェネリック
関数版です。class は object のクラスでなければなりません。
これらの関数は、ジェネリックであることに加えて、 obj のクラスが再定義されてもクラスの再定義を起動しない (そのような場合、classは obj の元々のクラスで なければならない)という点で手続き版とは違います。
覚書: CLOS とはちがい、slot-ref などは、その中でジェネリック
関数版を呼ぶことはありません。それゆえ、slot-ref-using-class を
特定化することによって、slot-ref をカスタマイズすることはできません。
つまり、これらのジェネリック関数の主たる目的は change-class
メソッドの内部で使われることです。とくに、slot-ref などは
クラス再定義を再度起動する(詳細については クラスの変更 を
参照)ので、obj の再定義中には使えません。
| [ < ] | [ > ] | [ << ] | [ Up ] | [ >> ] | [Top] | [Contents] | [Index] | [ ? ] |
CLOS系のオブジェクトシステムのユニークな機能は既存のインスタンスの クラスを変更できるということです。新旧二つのクラスに関連性がある必要は ありません。おのぞみなら、ミシンを雨がさに変更することもできます。
オブジェクト obj のクラスを new-class に変更します。
デフォルトのメソッドは単に change-object-class 手続きを呼ぶだけです。
オブジェクト obj のクラスを orig-class から new-class に変更します。これはジェネリック関数ではありません。 オブジェクトのクラスを変更するにはちょっとした秘密の内部的操作が必要で、 この手続きはそれを隠蔽しています。
クラスを変更する正確なステップは以下のようになっています。
allocate-instance によって
アロケートされる。
objに対してnew-classのinitilize メソッドは呼ばれないことに
注意してください。必要なら、独自の change-class メソッドを
定義してintializeを呼ぶようにすることができます。
change-object-class は obj を返します。
ユーザは大抵の場合、change-object-classを直接呼ぶ必要は無いでしょう。
そのかわり、特定化した change-class を定義するべきです。
たとえば、旧いクラスのスロット x を
新しいクラスのスロット y へ持ち越すことは、
こんな風に書けば可能です。
(define-method change-class ((obj <old-class>) <new-class>)
(let ((old-val (slot-ref obj 'x)))
(next-method) ;; calls default change-class
(slot-set! obj 'y old-val) ;; here, obj's class is already <new-class>.
obj))
|
再定義されたクラスのインスタンスが更新される場合は、それもクラス変更として
扱われます。オブジェクトは通常のスロットアクセサ/モディファイア
経由でアクセスされるときに、そのクラスが再定義されたかどうかを
チェックされます。もし再定義が行われていれば、
再定義されたクラスをnew-classとして
change-classが呼ばれます。
すなわち、インスタンスの更新はオブジェクトの
クラスを元々のものから再定義されたものへ変更することと看倣されます。
change-class を特定化することで、インスタンスを再定義された
クラス用に更新する方法をカスタマイズできます。しかし、クラス再定義
用の change-class を書くには特別な注意が必要です。
まず、再定義はクラスオブジェクトのグローバルな束縛を変更してしまいます。
それゆえ、クラス再定義がおこる前の旧いクラスへの参照を保持しておく
必要があり、change-class メソッドの特定化をするには、この旧い
クラスを使う必要があります。
;; save old <myclass> (define <old-myclass> <myclass>) ;; redefine <myclass> (define-class <myclass> () ...) ;; define customized change-class method (define-method change-class ((obj <old-myclass>) <myclass>) ... (next-method) ...) |
次に、上の change-class メソッドは、slot-ref、slot-set!、
class-of などを経由して、暗黙のうちに起動される得ることに
注意してください。もし、change-class 内で、obj に対して
再度 slot-ref のような手続き使うと、インスタンス更新プロトコルが
再帰的に起動され、無限ループをひきおこすことになります。インスタンス
更新を起動しないようなメソッドしか使えません。すなわち、
slot-ref-using-class、slot-set-using-class!、
slot-bound-using-class?、current-class-of しか使えません。
仮想スロットのように、手続きによって計算される値をもつスロットを
持ち越したいのであれば、slot-ref その他が、そのスロットの値を
計算している最中に、暗黙裏に obj に対して呼ばれることがありえます。
実際のところは、change-object-class はこのような再帰を検出する
保護機構をもっています。もし、このようなことが起これば、
change-object-class はそのスロットの値を取り出すのを諦め、
新しいインスタンスのスロットを旧いスロットが未束縛であるとして、
初期化します。
インスタンス更新をカスタマイズするのは非常に強力ですがたいへん
トリッキーな仕事です。Gauche のソース中のテストプログラム
には自明ではないいくつかのケースが含まれています。test/object.scm
見てみてください。
| [ < ] | [ > ] | [ << ] | [ Up ] | [ >> ] |
This document was generated by Shiro Kawai on October, 7 2008 using texi2html 1.78.