「オブジェクト」の項ではクラスオブジェクトのことに少しだけふれた。
ここではさらにその先へ進んでいく。まずはもう一度 struct RClass
と
RBasic
を見てみよう。
struct RClass {
struct RBasic basic;
struct st_table *iv_tbl;
struct st_table *m_tbl;
VALUE super;
};
struct RBasic {
unsigned long flags;
VALUE klass;
};
(ruby.h)
struct RClass
も struct RBasic
を要素に持つので、
RClass.basic.klass
がある。ここには何が入っているのだろうか? つ
まりオブジェクト Class
のクラスは? それが「特異クラス」というもの
だ。英語ならメタクラス。特異クラスと Class
の関係は、以下のよう
な図を見るとわかりやすい。
かっこつきのものが特異クラスだ。太い縦の矢印が継承関係で、細い 横の矢印がクラス──インスタンス関係である(矢印側がクラス)。重要な 点は二つだ。
Class
の下位クラスであり同時にインスタンスである
リファレンスマニュアルの Class
の項には
「より正確に言えば、…(略)…Class
はそのメタクラスのクラスです」
と書いてあるのだが、それはこの点を指して言っているわけだ。
またこの図にはもう一つ 「クラスオブジェクトの特異クラスはクラスに対応して継承する」 という重要な特徴が書かれているのだが、これについてはあとで触れる。
ではとりあえず「特異クラス」なるものの正体を見よう。特異クラスを
作る関数は 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.type
は (Class)
を返してくれる
はずだが、実際に試してみると普通の(特異クラスではない)Class
を返す。
これはなぜかというと、メソッド type
が特異クラスをスキップするように
なっているからである。普通のメソッドは特異クラスを隠蔽するようになって
おり、普通にスクリプトを書いている限りなかなか特異クラスにはお目に
かかれない。
だが、唯一それが露出している部分がある。それは特異クラス定義文だ。
class << Object
self
end
Rubyスクリプト実行中は、コードが書けるところならば必ずメソッドのデ
フォルトのレシーバ(self
)が存在する。クラス定義中はそのクラスが
self
になるので、特異クラス定義中は self
が特異クラスになっている
わけだ。例えば上の例の self
は (Object)
だ。ただし to_s
や name
で
見ても、type
の場合と同じように特異クラスがスキップされて
"Object"
になってしまうので違いがわからない。__id__
を使って確かめよう。
では任意の特異クラスを入手するにはどうすればよいだろうか。あまり知 られていないことだが実はクラス定義にも返り値がある。スコープの最後 の式の値が特異クラス定義の返り値なので、次のようにすれば特異クラス を返すメソッドが定義できる。
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()
で特異クラスにメソッドを定義すれば完了となる。
つまり特異メソッドが普通のメソッドと違うところは定義されるクラスだ
けでメソッド側になんら変わりはない。そして特異クラスの定義とは「あ
るインスタンス専用のクラスを普通のクラスとの間に一枚はさむ」作業だ
と総括できる。
Ruby には rb_define_class()
(class.c
)など、クラスを定義するための
関数がいろいろと用意されているが、この初期状態を作るのにはそれらの
関数は使えない。というのは、クラスを作るためにはその特異クラスを作
る必要があるが、特異クラスを作るためには Class
が必要だからだ。そ
こでまず最初は関数 boot_defclass()
で Object
、Module
、Class
を作成
する。
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;
}
(object.c)
rb_name_class()
の name
は動詞「命名する」で、引数のクラスオブジェクト
のインスタンス変数__classid__
にクラス名の ID
をセットする(これもやは
りスクリプトからは見えない変数)。また st_add_direct()
で ID
からクラ
スオブジェクトの対応を記憶する。この二つで ID
とクラスオブジェクトの双
方向変換ができるようになる。
あとは rb_singleton_class_new()
で (Object) (Module) (Class)
を作り、
直接 RClass
構造体をいじってセットすれば初期状態が完成する。
通常のクラス(Object Module Class
でも特異クラスでもないクラス)は
すべて rb_define_class()
、rb_define_class_id()
、
rb_define_class_under()
で定義する。..._id
は rb_define_class()
の
文字列のかわりに ID
を使うもの。..._under
はクラスやモジュールの「下」
にクラスを作るためのもの。..._class
と ..._under
はほとんど ..._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()
は boot_defclass()
と同じ。
そのあとが問題だ。前述したとおりクラスのクラスは特異クラスなの
だが、その特異クラスは、スーパークラスのクラス、つまり特異クラスだ。
どういうことかと言うと、クラス B (< A)
を定義すると B
のクラスは (B)
だが、(B)
は (A)
の下位クラスになる継承しているということだ。つまり
「クラスの特異クラスは、クラスの継承と同じように対応して継承する」
ということになる。まったく同じクラスツリーが、クラスと、そのクラスの
両方で形成されるのだ。これは最初のほうで見せたクラス関係図を見ると
よくわかる。
これは普通の特異メソッド定義では起きない現象で、クラスの特異クラスの
場合だけ起こる。なんのためにこんなことをするかというと、実は
クラスの特異メソッドを継承させるためである。最もわかり
やすいのは 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
だからどうってこともないけど、禁止されてるとやりたくなるんだよね。
ところで、これまでモジュール(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
に m1
をインクルードすると……
parent im2 ---- m2
| |
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
をスキップする。特異クラス
の場合のような例外的な手段もない。