Ruby で扱える全ての値はオブジェクト
とはよく言われるけど、これが実際には何なのか。
Rubyのオブジェクトチョットワカルって言いたいので調べた。
すごい長くなりそうなので(オブジェクトといっても構造体が複数種あるため)
まずは、自分たちが定義したクラスから生成されるオブジェクトの実体が何なのかから。
結論から言うと
クラス(RClass構造体)から生成されたオブジェクトの実体はRObject構造体である
???と言う事で、実際のコードを見てみる
// ruby/include/ruby/internal/core/robject.h#L48-L58 struct RObject { struct RBasic basic; union { struct { uint32_t numiv; VALUE *ivptr; void *iv_index_tbl; /* shortcut for RCLASS_IV_INDEX_TBL(rb_obj_class(obj)) */ } heap; VALUE ary[ROBJECT_EMBED_LEN_MAX]; } as; };
これだけ見ると割とシンプル
RBasic
・・・次で説明numiv
・・・インスタンス変数の数ivptr
・・・インスタンス変数の値へのポインタ。あくまで変数名は格納されず値のみiv_index_tbl
・・・インスタンス変数の変数名とivptr
の配列内の値を対応させるハッシュテーブル(st_table)へのポインタを格納。変数名はRClass構造体に格納される。
RBasicは
RBasic { VALUE flags; /**< @see enum ::ruby_fl_type. */ const VALUE klass; #ifdef __cplusplus public: RBIMPL_ATTR_CONSTEXPR(CXX11) RBIMPL_ATTR_ARTIFICIAL() RBIMPL_ATTR_FORCEINLINE() RBIMPL_ATTR_NOALIAS() /** * We need to define this explicit constructor because the field `klass` is * const-qualified above, which effectively defines the implicit default * constructor as "deleted" (as of C++11) -- No way but to define one by * ourselves. */ RBasic() : flags(RBIMPL_VALUE_NULL), klass(RBIMPL_VALUE_NULL) { } #endif };
これまたシンプルでRBasicはflags
とklass
の2つの値を持っている
flags
は、内部で多目的に使われているんですが、例えば構造体の型(後述のRStringやRArrayとか)を識別するのにも使っています。
klass
は、オブジェクトが何のインスタンスであるかを示すクラスポインタ。
以上をふまえて、超ざっくりで言うと
オブジェクト(RObject構造体)は、クラスへのポインタとインスタンス変数の集合体
なので、Class内に定義したmethodsはオブジェクトには含まれていないです。
...
具体的によくわかんねーよって感じになると思うので
実際のRubyコードと対応させて説明してみる。
class Person attr_accessor :name, :age end
上のシンプルなクラスからオブジェクトを生成する。
irb#1(main):001:0> hoge = Person.new => #<Person:0x007fe70187f7e0> irb#1(main):002:0* hoge.name = 'john' irb#1(main):003:0> hoge.age = 22 irb#1(main):004:0> hoge => #<Person:0x007fe70187f7e0 @name="john", @age=22>
この時の、#<Person:0x007fe70187f7e0>
部分のPerson
はクラスポインタの値で、16進数の0x007fe70187f7e0
はオブジェクトのポインタを示していて、これはオブジェクト毎にユニーク。
この生成されたオブジェクト毎にユニークなポインタがあるので、同じClassから生成されたオブジェクトが複数あってもオブジェクト毎に異なる値を追跡出来ているんですね
で、これらを対応させると...
RObject内のRBasicのklass
はPersonを示す、RClass構造体へのポインタ。
RObject内のRBasicのflags
は色々なフラグがあるけど、例えばRObject構造体を示すフラグとか。
numiv
は2。
ivptr
はjohnと22
iv_index_tbl
は@name, @ageとjohn,22を対応させるハッシュテーブルへのポインタ。
となる。
ここまでで、クラスから生成されたオブジェクトの実体についてざっくり説明したが、文字列や整数とかがRObject構造体と同じなのかと言うとそうではなくてまた別の構造体を使っています。
例えば文字列の値はRString構造体に保存していて、これまたオブジェクトなので文字列にインスタンス変数を持つことも出来ます。
※整数についてはまた話が長くなるので割愛
使う機会はないだろうけど、こんなことも出来ます。
irb#1(main):001:0> str = 'john' => "john" irb#1(main):002:0> str.instance_variable_set('@age', 22) => 22 irb#1(main):003:0> str.instance_variables => [:@age]
あと厳密に言うと、RObject構造体と同じようにRBasicは共通なんだけど、RStringはivptr
やらiv_index_tbl
は持っていないのでgeneric_iv_tbl
を使っています。
// ruby/include/ruby/internal/core/rstring.h#L73-L86 struct RString { struct RBasic basic; union { struct { long len; char *ptr; union { long capa; VALUE shared; } aux; } heap; char ary[RSTRING_EMBED_LEN_MAX + 1]; } as; };
// ruby/variable.c#L1217-L1231 static void generic_ivar_set(VALUE obj, ID id, VALUE val) { struct ivar_update ivup; ivup.iv_extended = 0; ivup.u.iv_index_tbl = iv_index_tbl_make(obj); iv_index_tbl_extend(&ivup, id); st_update(generic_iv_tbl, (st_data_t)obj, generic_ivar_update, (st_data_t)&ivup); ivup.u.ivtbl->ivptr[ivup.index] = val; RB_OBJ_WRITTEN(obj, Qundef, val); }
クラスから生成したオブジェクトの話からちょっと逸れたけど、おわり。
参考
https://github.com/ruby/ruby
http://i.loveruby.net/ja/rhg/book/object.html
Rubyのしくみ -Ruby Under a Microscope-