-
2007-09-19
管窥Ruby——类的变量(更新版)
版权声明:转载时请以超链接形式标明文章原始出处和作者信息及本声明
http://dreamhead.blogbus.com/logs/8338960.html
《管窥Ruby——类的变量》写在去年,写成之后便更新了一次,因为最初的描述存在一些偏差。即便如此,jxb8901依然指出了其中的一些不足。最近,dennis-zane再次提出了这个问题。回过头来仔细品味,确实有些地方写得不是很到位,索性把它重新写过。
管窥Ruby——类的变量
变量和方法是面向对象难以割舍的两个重要组成部分。在《管窥Ruby——类的方法》中,我们谈到方法,沿着这条路继续,我们再来看看类中的变量。
开始之前,我们还是要再次回顾RClass的定义:
struct RClass {
struct RBasic basic;
struct st_table *iv_tbl;
struct st_table *m_tbl;
VALUE super;
};
(ruby.h)
如果你看过《管窥Ruby——类的方法》,了解了方法存储方式,变量的存储方式便也一目了然了,同样的st_table,意味着同样的处理方式。
不得不承认的一点是,在讨论类的方法时,我故意忽略了一个事实:方法分为类的方法和实例的方法两种。如果对其它语言实现有些许了解,我们知道,这两种方法差别仅仅是this(C++或Java的说法),到了底层时,这个差别可以视为无物,可以统一存放。关于这点,有兴趣可以参考一下《深入Java虚拟机》,它的第五章讲解了虚拟机内方法的表现形式。在Ruby中,类的方法和实例的方法并不是存放在一起的,这里定义的实际上是实例方法,而类的方法是定义在类的Singleton类中。
遇到变量时,我们会碰到同样的问题:类的变量和实例变量是无法统一管理的。因为类的变量只有唯一的一份,而实例变量则是每个实例都有一份。所以,RClass存放的并不是真正实例变量。代码是说明问题的最好方式,下面这段代码说明了如何设置实例变量:
VALUE
rb_ivar_set(obj, id, val)
VALUE obj;
ID id;
VALUE val;
{
if (!OBJ_TAINTED(obj) && rb_safe_level() >= 4)
rb_raise(rb_eSecurityError, "Insecure: can't modify instance variable");
if (OBJ_FROZEN(obj)) rb_error_frozen("object");
switch (TYPE(obj)) {
case T_OBJECT:
case T_CLASS:
case T_MODULE:
if (!ROBJECT(obj)->iv_tbl) ROBJECT(obj)->iv_tbl = st_init_numtable();
st_insert(ROBJECT(obj)->iv_tbl, id, val);
break;
default:
generic_ivar_set(obj, id, val);
break;
}
return val;
},
(variable.c)
抛开前面那些复杂的东西,直接来看switch语句后面的内容。这里存在两种情况,对于存在T_OBJECT、T_CLASS和T_MODULE标记的,变量会写入iv_tbl,而其余的情况则转交 generic_ivar_set处理。iv_tbl是“实例变量表(instance value table)”的缩写,不过,如果当前对象是个类对象,这个“实例”变量实际上就是类变量,所以,这个名字多少有些名不符实。
除此之外,还有一个generic_ivar_set。下面是generic_ivar_set的实现:
static void
generic_ivar_set(obj, id, val)
VALUE obj;
ID id;
VALUE val;
{
st_table *tbl;
if (rb_special_const_p(obj)) {
special_generic_ivar = 1;
}
if (!generic_iv_tbl) {
generic_iv_tbl = st_init_numtable();
}
if (!st_lookup(generic_iv_tbl, obj, (st_data_t *)&tbl)) {
FL_SET(obj, FL_EXIVAR);
tbl = st_init_numtable();
st_add_direct(generic_iv_tbl, obj, (st_data_t)tbl);
st_add_direct(tbl, id, val);
return;
}
st_insert(tbl, id, val);
}
(variable.c)
同样忽略一些非主干的部分,我们看到,这段代码先在一个generic_iv_tbl中进行查找,用作查找键值的是对象实例(obj),而目标同样是一个st_table。得到这个表之后,利用变量名做键值将值插入到表中。我们便不难分析在这种做法中实例变量的存储方式。存在一个全局的实例变量表,走到这里的实例都在其中拥有一席之地,而实例变量存储在实例对应的表中。Ruby通过这样一个二级结构,解决了这些实例变量存储的问题。
了解过基本的做法之后,随之而来的一个问题就是,这些代码都会在什么情况下起作用。
代码已经说得很明白了,只有在有那几个标记的情况下,才会直接调用iv_tbl。T_CLASS和T_MODULE都很好理解,那什么情况下会有T_OBJECT呢?在C Ruby中,在Ruby层次上定义的类,生成的实例都是会标有T_OBJECT(参见《管窥Ruby——Allocator》)。所以,所有由Ruby层次上生成的对象都会走到这里来。
那generic_ivar_set呢?除了那几个标志外的其他部分都会走到这里。除此之外的标志表示什么呢?读一下代码我们便不难发现,几乎就是builtin的几个类,比如数组、字符串、正则表达式等等。那为什么这些builtin类没有一个iv_tbl。对于这些builtin而言,它们真正的实例变量都是以C的形式给出,所以,额外存在一个iv_tbl实际上是一种空间上的浪费。虽然不是常态,但我们依然可以为这些builtin类添加自己的实例成员。为了保持Ruby的动态特性,这才有了generic_ivar_set的存在。
引用地址:








评论
I would like to read your blog but I cant read chinese.. Can you post an english version. and syndicate it too...
Thanks
Sudhindra
Thank you for you interest!
You can try Google translate or Yahoo babel fish.