クラスとインスタンス

Squirrelのクラス定義は記述上ではテーブルの定義とほとんど同様である。ただし要素の動的削除はできない。追加書き込み専用である。ドキュメントにもあるとおり、クラスからインスタンスが初めて作成された時点で要素の追加や値の変更がロックされる。これによって同じクラス定義からは同じインスタンスだけが生じることが保証される。

クラスに追加される要素は、メンバ関数などインスタンスそれぞれに対して静的なものと、インスタンスがそれぞれの値を代入できる動的なものとの2種類に分別され別々の配列へと収められる。クラスが内包するハッシュテーブルには実際にはこの配列へのインデックスがその名前と紐付けられて収められている。

インスタンスはクラス定義への参照を共通して持ち、クラス定義の動的変数群の配列と同サイズの配列を保持する。インスタンスごと異なるメンバ変数の値はここへ収められる。インスタンスに対して要素検索が行われた場合、名前はインスタンスが参照を持つクラス定義内のハッシュテーブルを検索される。検索して見つかったインデックスをインスタンス内のメンバ変数値の配列へと適用して値を得る。

   |
  名前
   ↓
[クラス内のハッシュテーブル] 
   |
 配列インデックス
   ↓
[インスタンス内のメンバ変数の配列] 
   |
 メンバ変数の値 
   ↓

ちょっといい話

インスタンスのメンバ変数はSquirrelスクリプト側では名前で検索されるものでありながら実際には配列に入っているので、C++からはいちいちハッシュテーブルを検索しなくとも配列インデックスを決め打ちして値を参照してしまうこともできる。(Squirrelスクリプト側でもこのような危険なショートカットをできるようにしてもおもしろいと思う)インデックスは基本的に定義した順番になるが、どのように決まるのか詳しくはsqclass.cpp:46行目 SQClass::NewSlotを参照されたし。

// Squirrel
class A { 
  f = 3.14;
  i = 10;
}
a <- A();
// C++
// aをsq_getした後…
HSQOBJECT obj;
sq_getstackobj(v,-1,&obj);
float f = _float(_instance(obj)->values[0]);
int i = _integer(_instance(obj)->values[1]);

型とインデックスを決め打ちしている非常に危険なコードだが、このダイレクト感がSquirrelの魅力なのだ

クラス継承はベースクラスの参照を持つのでなく、継承した時点でのベースクラスのメンバがコピーされる(メンバ検索の高速化のためと思われる)ので…

class A {};
class B extends A {};
A.a <- 0;
print(B.a); /// ← AN ERROR HAS OCCURED [the index 'a' does not exist]

こうなる。

クラスの定義もSQObjectのとり得る型の一つにすぎず、実行時に定義したり、代入したりできるのでこんなコードを書くことも。

// クラスにあとからデフォルト実装をくっつける
function InsertDefaultImpl(target, base)
{
  local c = class extends base {};
  foreach(k,v in target) { c[k] <- v; }
  return c;
}
class B { b = "b"; }
class A { a = "a"; }
I <- InsertDefaultImpl(A,B);
print(I.a);
print(I.parent.b);