Reference

PowerMopsにはレファレンス(reference)という機構があります。それ自体は、オブジェクトのポインタを格納する変数のようなものです。しかし、この機構はC++(Java?)の"参照"(Reference)に似た働きをもっています。ただし、一応混同しないように、PowerMopsのレファレンスの方は、「参照」と訳さず、カタカナ書きのままにします。("リファレンス"の方がしっくりくるという方は、すみません。我慢してください^^;;)

具体的には二つあります。一つはメッセージバインドがバーチャルテープル(Virtual table)によるLate bind(いわゆる、「動的束縛」)になること。もう一つは、レファレンスを使ってヒープオブジェクトを生成することができ、そのオブジェクトはガベージコレクタで回収されるということです。ここでは、前者を扱います。

原則的動的束縛

レファレンスは、オブジェクト(インスタンス)と同じようにクラス名とともに宣言で生成します。ただ一つだけ違うところは、初めに"Ref"とつけるところです。
Ref ShinNihonClass myReference
最後が、生成されたレファレンスの名前です。

宣言された後は、レファレンスには普通のオブジェクトと同じようにメッセージ伝達(message sending -- なんて訳すんですかね)をコンパイルすることができます。レファレンスは それ自体は変数の一種です。つまり、ObjPtrに類似します。例えば、アレイのような項目数が必要なクラスに関してもObjPtrのときと同じで宣言時には項目数は必要ありません。実体の方がこれを決めるのです。宣言時点では、まだオブジェクトの実体はないので、メソッドを実行することはできません。コンパイル -- つまり、ワード定義の中か、メソッド定義の中に書く -- のみです。
: KIAI  1 2 3 dah: myReference  ;
レファレンスに対してメソッドを実行するよりも前に、レファレンス生成の際に宣言したクラスに属する真正のオブジェクトのアドレスを、レファレンスに格納しておかなければなりません。これは、
ShinNihonClass Inoki
......
addr: Inoki -> myReference
のように行います。オブジェクトにおいては、その名前を書くことはそのアドレスがスタックに積み込まれることを意味するので、"addr:"は省略できます。つまり、最後の行は
Inoki -> myReference
でもOKです。ワード定義の中でも書き方は同じです。これを実行した後に、先述のワード"KIAI"を実行すれば、
1 2 3 dah: Inoki
と同等の結果が得られるわけです。ただし、レファレンスにオブジェクトを格納する前にメソッドを実行してしまうと、クラッシュするので、この点は注意しましょう。

オブジェクトに直接メッセージを送る場合と比べ、レファレンスの場合はメッセージのバインドが特殊な動的バインドになるため、格納できるオブジェクトの種類にある種の柔軟性があります。例えば、"UFOClass"を"ShinNihonClass"のサブクラスとすると、
UFOClass Ogawa
として、ワード"KIAI"の実行前に
Ogawa -> myReference
を実行しておくと、もしも"UFOClass"の定義で"dah:"メソッドがオーバーライド(上書き再定義)されているとすれば、"Ogawa"に関しては、"UFOClass"のメソッドが適用されることになります。

ただし、"dah:"メソッドを上書きするには、"dah:"という名前でメソッドを再定義しなければならず、"hustle!hustle!:"ではダメです。高田総統にコケにされてしまいます(^^;;)。
わけが分からないたとえで、スミマセン。私も別に、プロレス、詳しいわけじゃありませんが(書いた時期が偲ばれる…)。

つまり、レファレンスは宣言時のクラスのサブクラス(ただし、第一順位単系継承のみのようです)に属するオブジェクトも格納できるようになっていて、メソッドは、スーパークラス-サブクラス関係でのオーバーライドを反映したメソッドが用いられるということです。このように、格納されたインスタンスのクラスに応じて異なるメソッドが用いられるという点が、動的束縛と呼ばれる所以です。

サブクラスでないクラスに属するオブジェクトのアドレスを格納しようとすると、その時点でエラーになり、プロセスは停止し、エラーメッセージが表示されます。

別の束縛方式の利用

レファレンスはいつもバーチャルテーブル方式による動的バインドでなければならないのか、というと、そうではありません。こういう選択肢の多さは、Mopsの特徴というか、癖のひとつですね(^^;;)。

動的バインドをやめて、静的バインドにしたいときは、レファレンスの宣言時に、"no_subclasses"とつけます。
ref aClass aReference no_subclasses
こうすれば、バインドは全て静的束縛となります。その代わり、"aClass"のサブクラスに属するオブジェクトのアドレスを格納することはできません。(エラーになります。)

また、何でもありの普通の動的バインドにしたいときには、クラス名を "Any"としてレファレンスを宣言します。
ref Any aReference2
"Any"はダミークラスで、こうしておけば、"aReference2"にはどのクラスのオブジェクトのアドレスを格納することもできるようになります。もっとも、当然のことではありますが、メッセージを理解できないクラスのオブジェクトを格納してメッセージ伝達を実行してしまうと、"そんなの知らない"エラーになります。

レファレンスは、メッセージに関する限りはオブジェクトと全く同じに扱うことができます。また、レファレンス名を書けば、格納されているオブジェクトのアドレスがスタックに置かれる点も、普通のオブジェクトと同じといえます。

バーチャルテーブルの仕組み

あまり詳しいところはわかりませんが、簡単に説明します。別に利用のために必要な話ではありません。

レファレンスは、単にオブジェクトのアドレスを格納するだけの変数ではなくて、他に二つのポインタを格納しています。ひとつは、ヒープオブジェクトを生成する(他ページで説明します)ときのヒープポインタ、もうひとつは、該当するクラスのバーチャルテーブルのポインタ(アドレス)です。つまり、三つのポインタを保持しているわけです。

バーチャルテーブルというのは、要するに、各メソッドのコードがある場所のポインタをアレイにしたものです。これは、クラス定義の仕上げのところで、クラス毎にそのメソッドについて記録されます。スーパークラスとサブクラスではクラス定義のある場所が違うわけですから、バーチャルテーブルのある場所(アドレス)は異なります。ですが、サブクラスのバーチャルテーブル(アレイ)の作り方がミソです。サブクラスの定義の際は、まず、クラスポインタを経由して上クラスのバーチャルテーブルをコピーしてきます。そして、クラスの上流のどこかのメソッドを自分で再定義していることがわかった場合は、同名のメソッドのポインタを上書きするわけです。新規名のメソッドだけ後ろに追加されます。こうすると、スーパークラス、サブクラス間で、同じ名前のメソッドのポインタが格納されている場所は、それぞれのバーチャルテーブルの頭から数えると、いつも同じ場所にあることになります。つまり、テーブルポインタからのオフセットでメソッドを特定できるわけです。そして出来上がったアレイを、自分のクラスの仕上げのところに写すわけです。

オブジェクトアドレスをレファレンスに格納するときには、実は、オブジェクトだけではなく、そのクラスのバーチャルテーブルのアドレスも適宜入れ替えています。レファレンスにメッセージを送るコードをコンパイルするときには、レファレンスのクラスデータ(辞書内の、レファレンスの場所から一定のオフセットのところにある)を使って、そのメッセージに対応するメソッドの、バーチャルテーブル内での頭からのオフセットを調べて保存しておきます。実行時には、レファレンスからオブジェクトアドレスと、バーチャルテーブルアドレス、それからコンパイル時に取ってあったオフセットを取り出してメソッドをバインドします。ランタイム時は、格納されているオブジェクトがサブクラスに属するかスーパークラスに属するかで、バーチャルテーブルのアドレスは違います。ですが、オフセットを足せばそれぞれのクラスの同名のメソッドに辿り着けるわけです。実質的なメソッド検索はコンパイル時に終わっているということです。そのため、この方式での動的バインドは、一般の動的バインドより実行が速いわけです。Mopsマニュアルには次のように書かれています:

スピードの比較に関していうと、静的束縛では、2~3個(a couple of)のマシンインストラクションしか要しませんが、レファレンスによるバーチャルテーブルバインドには、およそ30個かかります。動的束縛では500個ぐらいかかる場合もあり得ます。ただし、動的バインドではキャッシュを使ってあります。これはとても効果的で、オーバーヘッドを、大抵の場合に100インストラクションぐらいに低減してくれています。

まあ、500インストラクションあったって、うちの古い機械でもよほど詰まったりしない限り1000分の1秒未満なはずなわけで、とくに、ユーザインターフェイス関係に使うなら、その程度のオーバーヘッドはものの数ではありません。その水準でいくと、バーチャルテーブルの動的バインドが、実行速度の点でいかに効率的かがわかりますね。現在普及している諸言語の中で唯一"効率を重んじる文化"を持つといわれるC言語系(C++)で採用された方式(細部は全然違う可能性大ですが)というのも、うなずけます。


関連項目:






最終更新:2019年01月25日 22:47