「オブジェクト」の項ではクラスオブジェクトのことに少しだけふれた。
ここではさらにその先へ進んでいく。まずはもう一度 struct RClass と
RBasic
を見てみよう。
struct RBasic { unsigned long flags; VALUE klass; }; struct RClass { struct RBasic basic; struct st_table *iv_tbl; struct st_table *m_tbl; VALUE super; }; (ruby.h)
struct RClass も struct RBasic
を要素に持つので、
RClass.basic.klass がある。ここには何が入っているのだろうか? つ
まり Object
や Array
それ自体のクラスは? まずは普通に Ruby スク
リプトから見てみよう。
p Object.class p Array.class p Hash.class p Class.class
結果はすべて Class になる。つまりこんな感じだ。
Object ---> Class | | Module ---> Class | | Class ---> Class
縦の線が継承、横がオブジェクト〜クラスだ。Class のクラスは また Class だから、ここで無限ループができていることになる。 つまりインスタンス〜クラス関係に注目して図を書くとこうだ。
とあるオブジェクト → Object → Class → Class → Class → .....
なぜループしていなくてはいけないか。まず、Ruby ではすべてのデー タはオブジェクトである。そしてまた Ruby ではクラスもデータなの でクラスもオブジェクトである。オブジェクトはメソッドによって定 義されるので、逆に言うとオブジェクトはなんらかのメソッドに反応 できなくてはならない。そして Ruby ではメソッドに反応するために はなんらかのクラスに属さなければいけない。従って、あらゆるオブ ジェクトはどこかのクラスのインスタンスでなければならない。ここ から、クラス自身もクラスを持たなくてはいけないとわかる。
ではこのことを踏まえて現実に実装することを考えてみる。最も単純 で素直なのは、Class のクラスは ClassClass、ClassClass のクラス は ClassClassClass ……というふうに、概念を表現するクラスを次々 と連鎖することである。しかしこれではちょっと実装がめんどくさい。 そんなわけでクラスがオブジェクトであるオブジェクト指向言語は、 Class のクラスを Class 自身にしてしまうことでループを作りだし、 無限のインスタンス〜クラス関係を仮想的に作りだすのが常套手段な のである。
補注:
この二つは切り離して考えなければいけない。クラスがオブジェクト
であるのは、あくまで Ruby プログラムに自分自身 (プログラムコー
ド) を操作できる能力……リフレクション (reflection)
と言う……
を与えるためである。プログラムはデータしか変更できない (または、
変更できるものをデータと言う)。そしてデータは Ruby ではオブジェクト
としてしか存在できない。だからクラスがオブジェクトなのである。
さて、じゃあ RClass.basic.klass には Class が入ってるんだね!
これで解決!……と思いきや、これが違うのだ。printf
を使って見
てみると、RBASIC(rb_cObject)->klass と rb_cClass は違う値にな
る。実は RClass.basic.klass に入っているのは「特異クラス」とい
うものである。
これがナニか、というのはひとまず置いておいて、まずは現実の姿を 見ることにしよう。Ruby のクラスまわりのオブジェクトは以下のよ うなリンクを形成している。
かっこつきのものが特異クラスだ。太い縦の矢印が継承関係で、細い 横の矢印がインスタンス〜クラス関係である(矢印側がクラス)。重要な 点は二つだ。
またこの図にはもう一つ 「クラスオブジェクトの特異クラスはクラスに対応して継承する」 という重要な特徴が書かれているのだが、これについてはあとで触れる。
まず「特異クラス」なるものの正体を見よう。特異クラスを
作る関数は rb_singleton_class_new()
だ。
VALUE rb_singleton_class_new(super) VALUE super; { VALUE klass = rb_class_new(super); FL_SET(klass, FL_SINGLETON); return klass; } (class.c)
rb_class_new()
は Class のインスタンスを生成する関数だから、普通の
クラスと特異クラスの違いは FL_SET(...)
だけということになる。
FL_SET
は RBasic
の flags
をセットするマクロなので、つまり実装の観
点からは特異クラスとは FL_SINGLETON
フラグがセットされたクラスだと
言うことができる。
さて、ではなぜ Class.class が (Class) を返さないかというと、メ
ソッド class()
が特異クラスをスキップするようになっているからで
ある。普通のメソッドは特異クラスを隠蔽するようになっており、普
通にスクリプトを書いている限りなかなか特異クラスにはお目にかか
れない。だが、唯一それが露出している部分がある。それは特異クラ
ス定義文だ。
class << Object self end
Rubyスクリプト実行中は、コードが書けるところならば必ずメソッドのデ
フォルトのレシーバ(self
)が存在する。クラス定義中はそのクラスが
self
になるので、特異クラス定義中は self
が特異クラスになっている
わけだ。例えば上の例の self
は (Object)
だ。ただし to_s
や name
で
見ても class()
の場合と同じように特異クラスがスキップされて
"Object"
になってしまうので違いがわからない。__id__
を使って確かめよう。
※ 1.7
からは #
では任意の特異クラスを入手するにはどうすればよいだろうか。あまり知 られていないことだが実はクラス定義にも返り値がある。スコープの最後 の式の値が特異クラス定義の返り値なので、次のようにすれば特異クラス を返すメソッドが定義できる。
def singleton_class_of( obj ) class << obj; self end end p singleton_class_of(Object.new).__id__ p Object.__id__
ただし特異クラス定義の構文は開始した時点で問答無用で特異クラスを作る ため、引数に渡されたオブジェクトすべてに特異クラスを導入してしまう。 あくまで実験して楽しむ範囲にとどめておき、実際に活用したりしないほうがよい。
特異クラスはそもそも特異メソッドの実装のために作られたものだ。 ここでは実際に特異メソッドが定義される過程を見て、特異クラスについ てさらに理解を進めてゆこう。
まず最初は穏当に rb_define_singleton_method()
から始める。
void rb_define_singleton_method(obj, name, func, argc) VALUE obj; const char *name; VALUE (*func)(); int argc; { rb_define_method(rb_singleton_class(obj), name, func, argc); } (class.c)
rb_define_method()
は拡張モジュールを定義するのにも使うごく普通の
関数だから、どうやら rb_singleton_class()
というのがキモのようだ。
この関数はすぐ上に定義されている。
VALUE rb_singleton_class(obj) VALUE obj; { if (rb_special_const_p(obj)) { rb_raise(rb_eTypeError, "can't define singleton"); } if (FL_TEST(RBASIC(obj)->klass, FL_SINGLETON)) { return RBASIC(obj)->klass; } RBASIC(obj)->klass = rb_singleton_class_new(RBASIC(obj)->klass); rb_singleton_class_attached(RBASIC(obj)->klass, obj); return RBASIC(obj)->klass; } (class.c)
rb_special_const_p()
は util.c
にある関数で、VALUE
が即値(ポインタ
でない)であるオブジェクトすなわち nil
や Fixnum
でに対して真を返す。
これを弾いているのは、つくりを考えれば明らかだろう。対応する構造体
がなければ struct RBasic
がないわけで、特異クラスを格納できない。
ということはしかし逆に言うと Bignum
や Float
は OK
ってことになる。
もっともそんなことをしてもあまり役に立ちそうにはない。
次の if
でオブジェクトのクラスにフラグ FL_SINGLETON
が立っているか
(特異クラスかどうか)テストして、特異クラスだったらそれをそのまま返
す。そうでなかったら、rb_singleton_class_new()
で新しく特異クラス
を作成し、同時にそれをオブジェクトのクラスにする。このとき、特異ク
ラスは現在のクラスの下位クラスとして作られるので、それまでに定義さ
れているメソッドは自動的に継承されるわけだ。そのあとの
rb_singleton_class_attached()
はなんだろうか? 見てみよう。
void rb_singleton_class_attached(klass, obj) VALUE klass, obj; { if (FL_TEST(klass, FL_SINGLETON)) { if (!RCLASS(klass)->iv_tbl) { RCLASS(klass)->iv_tbl = st_init_numtable(); } st_insert(RCLASS(klass)->iv_tbl, rb_intern("__attached__"), obj); } } (class.c)
フラグ FL_SINGLETON
が立っているのが特異クラスだったから、この場合
最初の if
は真だ。どういうときに偽になるのかわからなかったので
検索してみたところ、最新のバージョンだと nil
や false
に特異メソッドを
定義できるようになっていて、そのときに偽になるようだ。
さて、if
が真になったときは、特異クラスオブジェクトのインスタ
ンス変数 __attached__
にそのインスタンスを代入している。実は暗
黙のルールとして「特異クラスはひとつしかインスタンスを持たない」
ことになっているので、特異クラスとそのインスタンスは一対一に関
連付けできるのだ。その対応がここでなされている。
__attached__
にはプリフィクスの@がついていないが、インスタン
ス変数テーブルに格納されるのでいちおうインスタンス変数というこ
とになる。このようなインスタンス変数は Ruby スクリプトレベルか
らは絶対に参照できない変数だ。
またこの __attached__
を参照しているのは主に eval.c
のようだ。
特異メソッドが削除されたり追加されたときにフックメソッドを(イ
ンスタンスに対して)呼んだり、クラス変数を実装するために使った
りしている。
まとめると、rb_singleton_class()
は、あるオブジェクトの特異クラスを
(必要なら作成して)返すということがわかる。そのあとは通常通りの
rb_define_method()
で特異クラスにメソッドを定義すれば完了となる。
つまり特異メソッドが普通のメソッドと違うところは定義されるクラスだ
けでメソッド側になんら変わりはない。そして特異クラスの定義とは「あ
るインスタンス専用のクラスを普通のクラスとの間に一枚はさむ」作業だ
と総括できる。
クラスを定義するための基本 API
は rb_define_class()
と
rb_define_class_under()
の二つである。拡張モジュールはもちろん
として、基本クラスライブラリまでがすべてこの二つだけで定義され
ている。他に rb_define_class_id()
という API
もあるが、これは
インタプリタ内部用である。前述の rb_define_class() _under()
が
これを使っているほか、Ruby スクリプトで class
文を書くとインタ
プリタがこの関数を呼ぶ。
ではこの三つを読んで具体的に違いを確認していこう。まずは内部に
近い rb_define_class_id()
から読み進めていく。
VALUE rb_define_class_id(id, super) ID id; VALUE super; { VALUE klass; if (!super) super = rb_cObject; klass = rb_class_new(super); rb_name_class(klass, id); /* 特異クラスを作る */ RBASIC(klass)->klass = rb_singleton_class_new(RBASIC(super)->klass); rb_singleton_class_attached(RBASIC(klass)->klass, klass); rb_funcall(super, rb_intern("inherited"), 1, klass); return klass; } (class.c)
rb_class_new()
はもう説明したとおり。クラスオブジェクトを生成する
(オブジェクトを作るだけで、リンクは形成しない)。
rb_name_class()
の name
は動詞「命名する」だ。知っていると思う
がクラスはクラス名と同名の定数に代入され、またクラスから名前を
知ることもできる。その関連付け(の片方)を行うのがこの関数。
void rb_name_class(klass, id) VALUE klass; ID id; { rb_iv_set(klass, "__classid__", ID2SYM(id)); }
見てのとおり。__classid__
にクラス名の ID
をセットする。
__classid__
もやはりスクリプトからは見えない変数である。
しかし定数への代入は行っていない。だがこの操作が
rb_define_class()
と……_under()
とで違うのは明らか
なので、それぞれの関数でやっているはずだ、と推測できる。
さて、その後が問題だ。
/* 特異クラスを作る */ RBASIC(klass)->klass = rb_singleton_class_new(RBASIC(super)->klass); rb_singleton_class_attached(RBASIC(klass)->klass, klass);
前述したとおりクラスの klass
は特異クラスなのだが、その特異ク
ラスのスーパークラスは、スーパークラスの klass
、つまり特異クラ
スだ。ああややこしい。図に書こう。
SomeClassA ---> (SomeClassA) | | SomeClassB ---> (SomeClassB)
こういうふうに継承をすると、
SomeClassA ---> (SomeClassA) | | | | SomeClassB ---> (SomeClassB)
こうなるということだ。つまり「クラスの特異クラスは、クラスの継 承と同じように対応して継承する」のである。まったく同じクラスツ リーが、クラスと、それに対応する特異クラスの両方で形成されるの だ。これは普通の特異メソッド定義では起きない現象で、クラスの特 異クラスの場合だけ起こる。
なんのためにこんなことをするかというと、実はクラスの特異メソッ
ドを継承させるためである。もっと言うと Object.new
のためである。
Object.new
は特異メソッドなので単純に継承しただけでは
SomeClass.new が定義されない。これでは不便だ。ていうかそれどこ
ろではなく、スクリプトレベルで定義したクラスのインスタンスが全
く生成できなくなる。そういうわけでクラスの特異クラスだけはクラ
スツリーと連動するようになっているのである。
それと最後の rb_funcall()
は Ruby のメソッドを呼ぶ関数で、ここ
では Class#inherited を klass
とともに呼んでいる。こんなもん何
に使うのかなぁ、と思ったのだが……使ってた。
rb_class_s_inherited
(object.c
)だ。Class の特異メソッド(すな
わち (Class) のメソッド)で、デフォルトの実装は例外を発生す
るようになっている。つまり Class を継承するのを禁止している。
ということは、このメソッドを再定義してしまえば
Class の下位クラスを作れるわけだ。やろう。
def Class.inherited( klass ) end class AClass < Class end p AClass.ancestors
だからどうってこともないけど、禁止されてるとやりたくなるんだよね。
ここまででクラスを作ることはできた。あとはクラスとその名前(定数)を リンクする作業だが、これが想像以上にめんどくさい。かなりの長丁場になる。
まずは rb_define_class()
だ。
VALUE rb_define_class(name, super) const char *name; VALUE super; { VALUE klass; ID id; id = rb_intern(name); if (rb_const_defined(rb_cObject, id)) { klass = rb_const_get(rb_cObject, id); if (TYPE(klass) != T_CLASS) { rb_raise(rb_eTypeError, "%s is not a class", name); } if (rb_class_real(RCLASS(klass)->super) != super) { rb_raise(rb_eNameError, "%s is already defined", name); } return klass; } klass = rb_define_class_id(id, super); st_add_direct(rb_class_tbl, id, klass); return klass; }
大きく rb_define_class_id()
の前後に分かれる。前ではすでに
同名のクラスが定義されていないかどうかチェックして、定義さ
れていたら追加定義の条件を満たすかどうか確認する。だめなら
ハネる。大丈夫ならそれを返して終了(新しいクラスを定義しない)。
後の st_add_direct()
が先程言っておいた、定数にクラスを代入す
る操作である。しかし、これを見れば明らかなとおり、どう見て
も定数に代入しているようには見えない。実はトップレベルのクラス
は通常の定数とは別に管理されており、定数を参照するときに
rb_class_tbl
を追加で検索するようになっているのだ。わざわざ別
になっているのは String
などの基本クラスを置き換えられたくな
いからであろう。
これでネストレベル 1 の場合の定数・クラス変換はできるようになった。
あとはネストレベル 2 以上の場合で、これはもっと複雑だ。レベル 2 以上の
クラスを定義するのは rb_define_class_under()
である。
VALUE rb_define_class_under(outer, name, super) VALUE outer; const char *name; VALUE super; { VALUE klass; ID id; id = rb_intern(name); if (rb_const_defined_at(outer, id)) { klass = rb_const_get(outer, id); if (TYPE(klass) != T_CLASS) { rb_raise(rb_eTypeError, "%s is not a class", name); } if (rb_class_real(RCLASS(klass)->super) != super) { rb_raise(rb_eNameError, "%s is already defined", name); } return klass; } klass = rb_define_class_id(id, super); rb_const_set(outer, id, klass); rb_set_class_path(klass, outer, name); return klass; }
構造は rb_define_class()
と同じで、rb_define_class_id()
呼びだ
しの前が再定義チェック、後が定数←→クラスの相互リンク作成であ
る。前半はつまらないのでとばす。後半は rb_set_class_path()
が
重要である。
void rb_set_class_path(klass, under, name) VALUE klass, under; const char *name; { VALUE str; if (under == rb_cObject) { str = rb_str_new2(name); } else { str = rb_str_dup(rb_class_path(under)); rb_str_cat2(str, "::"); rb_str_cat2(str, name); } rb_iv_set(klass, "__classpath__", str); }
最後の一行以外は「クラスパス」を作っている。クラスパスとは例え
ば「Net::NetPrivate::Socket
」のように、トップレベルからのネス
ト情報をすべて含んだ名前のことである。この場合「クラス名」は
「Socket
」である。
そして最後の一行で、クラスオブジェクトのインスタンス変数
__classpath__
に代入している。これも __classid__
と同じくスクリ
プトからは見えないインスタンス変数だ。
さて、実はこの前半、クラスパスの作成が予想外にやっかいだ。
この関数内の作業はいいとして、rb_class_path()
に進んでいこう。
VALUE rb_class_path(klass) VALUE klass; { VALUE path = classname(klass); if (path) return path; else { char buf[256]; char *s = "Class"; if (TYPE(klass) == T_MODULE) s = "Module"; sprintf(buf, "#<%s 0lx%lx>", s, klass); return rb_str_new2(buf); } } (variable.c)
else
節が対応しているのは明らかに名無しクラスの場合だ。
つまり classname()
は、クラスがすでに定数に代入されている場合にはその
パスを返し、でなければ NULL
を返すらしい。続いてその classname()
。
static VALUE classname(klass) VALUE klass; { VALUE path = Qnil; ID classpath = rb_intern("__classpath__"); klass = rb_class_real(klass); if (!klass) klass = rb_cObject; if (ROBJECT(klass)->iv_tbl && !st_lookup(ROBJECT(klass)->iv_tbl, classpath, &path)) { ID classid = rb_intern("__classid__"); if (st_lookup(ROBJECT(klass)->iv_tbl, classid, &path)) { path = rb_str_new2(rb_id2name(SYM2ID(path))); st_insert(ROBJECT(klass)->iv_tbl, classpath, path); st_delete(RCLASS(klass)->iv_tbl, &classid, 0); } } if (NIL_P(path)) { path = find_class_path(klass); if (NIL_P(path)) { return 0; } return path; } if (TYPE(path) != T_STRING) rb_bug("class path is not set properly"); return path; } (variable.c)
初っ端からたらいまわしである。
VALUE rb_class_real(cl) VALUE cl; { if (TYPE(cl) == T_ICLASS) { cl = RBASIC(cl)->klass; } while (FL_TEST(cl, FL_SINGLETON) || TYPE(cl) == T_ICLASS) { cl = RCLASS(cl)->super; } return cl; } (object.c)
T_ICLASS は、後述するとおりモジュールに関連して生成される内部
処理用のクラス。一方の FL_SINGLETON
なクラスはもちろん特異クラ
スだ。すなわち rb_class_real()
は最も近い通常クラスを返す。
とするとしかし、classname()
の if (!klass)
が真になることは
ないはずである。なぜなら、rb_class_real()
の cl
が NULL
になったら
FL_TEST
の時点で落ちるからだ。
では次の if
。
if (ROBJECT(klass)->iv_tbl && !st_lookup(ROBJECT(klass)->iv_tbl, classpath, &path)) { ID classid = rb_intern("__classid__"); if (st_lookup(ROBJECT(klass)->iv_tbl, classid, &path)) { path = rb_str_new2(rb_id2name(SYM2ID(path))); st_insert(ROBJECT(klass)->iv_tbl, classpath, path); st_delete(RCLASS(klass)->iv_tbl, &classid, 0); } }
条件のひとつめの逆、iv_tbl
がないというのは、
名無しクラスの場合である。なぜなら名無しでない場合 __classid__
を
代入するために iv_tbl
が存在するはずである。
条件のふたつめ、クラスパスがすでにセットされていないというのは、
いま始めて定義するクラスの場合、だろう。
合わせると、この if
が真になるのはごくごくフツーのクラスの場合である。
この場合は __classid__
を索いてそれを dup
、__classpath__
を
セット(キャッシュ)して __classid__
を消す。
次の if
。
if (NIL_P(path)) { path = find_class_path(klass); if (NIL_P(path)) { return 0; } return path; }
path
が nil
になるのは以下の場合だ。
__classpath__
がない__classid__
がない後者二つが成立するのは特異クラスに他ならない。
このあたりはかなり呼び出し関係が深かったので表にしておく。
rb_define_class_under rb_set_class_path rb_class_path classname rb_class_real find_class_path st_foreach (fc_i) fc_path
残るは Object Module Class の三つだ。通常の rb_define_class()
(class.c
)などの通常の API
ではこの三つのクラスを作成できない。
というのは、クラスを作るためにはその特異クラスを作る必要がある
が、特異クラスを作るためには Class が必要だからだ。しかしもち
ろんClass はまだできていない。そこで Ruby はこの三つだけは特別
扱いで生成する。
void Init_Object() { VALUE metaclass; rb_cObject = boot_defclass("Object", 0); rb_cModule = boot_defclass("Module", rb_cObject); rb_cClass = boot_defclass("Class", rb_cModule);
まず前半から。これは boot_defclass
が全てを語ってくれる。
static VALUE boot_defclass(name, super) char *name; VALUE super; { extern st_table *rb_class_tbl; VALUE obj = rb_class_new(super); ID id = rb_intern(name); rb_name_class(obj, id); st_add_direct(rb_class_tbl, id, obj); return obj; }
rb_define_class()
などに比べると非常に簡単である。
ここまでで定数・クラスの相互参照が確立し、リンクは
以下のようになっている。
Object --> NULL | Module --> NULL | Class --> NULL
以下は Init_Object()
の続き。
metaclass = RBASIC(rb_cObject)->klass = rb_singleton_class_new(rb_cClass); rb_singleton_class_attached(metaclass, rb_cObject); metaclass = RBASIC(rb_cModule)->klass = rb_singleton_class_new(metaclass); rb_singleton_class_attached(metaclass, rb_cModule); metaclass = RBASIC(rb_cClass)->klass = rb_singleton_class_new(metaclass); rb_singleton_class_attached(metaclass, rb_cClass);
特異クラスを生成し直接構造体にねじこむのみである。単純だ。
ところで、これまでモジュール(Module
クラスのインスタンス)がほとんど
出てこなかった。Module
は Class の上位クラスなのに、クラスのほうがたくさん
出てきて偉そうだ。これからモジュールの特徴であるインクルードについて
調べよう。
オブジェクトの章では、それぞれのオブジェクトが
ポインタ(VALUE
)と構造体の組で表されること、その構造体には自身の
クラスを格納するメンバ value->basic->klass
があることを知った。
klass
はただひとつしかなかったので、Ruby は単一継承らしいとわかる。
しかし実はモジュールのインクルードは概念的には多重継承であり、イン
クルードの際に多少加工して単一継承のように見せかけているにすぎない。
では早速その様子を見てみよう。インクルード操作の実体は
rb_include_module()
だ。……ただし
本当に正確に言うと、rb_mod_inclucde() (eval.c)
が include
の実体で、
そこから Module#append_features
が呼ばれ、そのデフォルトの実装が
rb_include_module()
を呼ぶようになっている。
やや長いので、少しづつ。最初にいろいろとチェック部分があって、
void rb_include_module(klass, module) VALUE klass, module; { VALUE p; int changed = 0; rb_frozen_class_p(klass); if (!OBJ_TAINTED(klass)) { rb_secure(4); } if (NIL_P(module)) return; if (klass == module) return; switch (TYPE(module)) { case T_MODULE: case T_CLASS: case T_ICLASS: break; default: Check_Type(module, T_MODULE); }
以下が本処理。klass
に、module
をインクルードする。
OBJ_INFECT(klass, module); while (module) { /* 既にスーパークラスで module をインクルードしていたらスキップする */ for (p = RCLASS(klass)->super; p; p = RCLASS(p)->super) { if (BUILTIN_TYPE(p) == T_ICLASS && RCLASS(p)->m_tbl == RCLASS(module)->m_tbl) { if (RCLASS(module)->super) { rb_include_module(p, RCLASS(module)->super); } if (changed) rb_clear_cache(); return; } } /*A*/ RCLASS(klass)->super = include_class_new(module, RCLASS(klass)->super); /*B*/ klass = RCLASS(klass)->super; module = RCLASS(module)->super; changed = 1; } if (changed) rb_clear_cache(); } (class.c)
最初の for
文がやっていることはコメントに書いてあるので、まずは
読みとばそう。その次を読むと、どうやらインクルード対象のクラスのスーパークラスを
差し替えているようである(A
)。さらにそのあとは、差し替えたばかりの
クラスと module
の super
に対して繰り返しているようだ。
module
の super
に入っているのは、module
にインクルードした
モジュールだろう(勘)。ということは B
の部分はただのループ
カウンタのインクリメントにすぎないので、A
の include_class_new()
が
重要なようだ。
static VALUE include_class_new(module, super) VALUE module, super; { NEWOBJ(klass, struct RClass); /* A */ OBJSETUP(klass, rb_cClass, T_ICLASS); if (!RCLASS(module)->iv_tbl) { /* B */ RCLASS(module)->iv_tbl = st_init_numtable(); } klass->iv_tbl = RCLASS(module)->iv_tbl; klass->m_tbl = RCLASS(module)->m_tbl; klass->super = super; /* C */ if (TYPE(module) == T_ICLASS) { /* D */ RBASIC(klass)->klass = RBASIC(module)->klass; } else { RBASIC(klass)->klass = module; } OBJ_INFECT(klass, module); OBJ_INFECT(klass, super); return (VALUE)klass; }
まず新しいクラスを作り(A
)、続いて module
のインスタンス変数
テーブル・メソッドテーブルをそれに移植(B
)、スーパークラスを
これまでのスーパークラス(super
)にする(C)。つまり、module
の
「化身」になるクラスを作って返すようだ。
また A
のところでは構造体フラグを T_ICLASS というのにしており、
これが化身クラスの印らしい。ICLASS の I
は include
だろうか?
さらに D
は、ちょっとわかりにくいのだが、rb_include_module
に
あった while
ループの二回目以降に対処している。一回目は module
は
T_ICLASS ではない(T_MODULE
だ)ので RBASIC(klass)->klass には
その実体のモジュールがセットされる(if
の偽側)。二回目以降は
module
は最初の module
にインクルードされているモジュールだから、
それはすでに T_ICLASS 化されているはずで、その RBASIC(klass)->klass には
実体がセットされているはずである。なのでそれを取り出して使う(if
の真側)。
具体例で見よう。まず m1
に m2
をインクルードすると……
im2 ---- m2 | m1
こうなるだろう。ここに、以下のようなクラス c1
を持ってくる。
parent im2 ---- m2 | | c1 m1
c1
に m1
をインクルードしよう。
まず一回目のループ (module = m1)
で以下のように変わり、
parent im2 ---- m2 | | im1 ---- m1 | c1
二回目のループ (module = im2)
でこうなる。
parent | im2' ---------------------- m2 | im2 ---- | | im1 -------------- m1 | c1
さてここまでくると rb_include_module()
の読み飛ばしていた部分が
理解できる。
/* 既にスーパークラスで module をインクルードしていたらスキップする */ for (p = RCLASS(klass)->super; p; p = RCLASS(p)->super) { if (BUILTIN_TYPE(p) == T_ICLASS && RCLASS(p)->m_tbl == RCLASS(module)->m_tbl) { if (RCLASS(module)->super) { rb_include_module(p, RCLASS(module)->super); } if (changed) rb_clear_cache(); return; } }
親(p
)のうち T_ICLASS で(つまりモジュールで)同じメソッドテーブルを
持つものがあったらそれは同じモジュールを二回インクルードしたという
ことなのでスキップする。ただしそのモジュールがインクルードしている
モジュールがあったらそっちも再確認する、というわけだ。
p
はすでにインクルードしたモジュールなのだから、それにインクルード
されたモジュールもすでにインクルードしているはず……と一瞬思ったが、
以下のような状況がありうる。
module M1; end module M2; end class C include M2 # まだ M1 はインクルードされてない end module M2 include M1 end class C include M2 # 今度はインクルードされている! end
逆に言うと、include
は「リアルタイム」でない。つまり、include
した
結果が即座に反映しないことがある。
また以上のような仕組みなので、モジュールの特異メソッドはインクルード
先クラスオブジェクト(またはモジュール)に継承しない。継承させるには
特異クラスもいっしょにつなぎかえないといけないが、それはやっていな
かったからだ。というわけで特異メソッドを定義したいときは
Module#append_features
をオーバーライドするのが常套手段だ。
もうひとつ考えることがある。どうやら RBasic(iclass)->klass
は単に
実体のモジュールを示すためだけに使われているようなので、ICLASS に
対してメソッドが呼ばれたりする状況は非常にまずい。だから ICLASS
は絶対にスクリプトから見えてはならないはずである。そして実際
Object#type
など全てのメソッドは ICLASS をスキップする。特異クラス
の場合のような例外的な手段もない。
Copyright (c) 1998-2002 Minero Aoki
<aamine@loveruby.net>
This site is link free.