Ruby元类:为什么三个定义单例方法时?
让我们计算MRI范围内的类:
def count_classes ObjectSpace.count_objects[:T_CLASS] end k = count_classes
使用类方法定义类:
class A def self.foo nil end end
并运行:
puts count_classes - k #=> 3
请解释一下,为什么三个?
查看MRI代码,每次创建一个Class
,在Ruby中类型为Class
对象时,ruby会自动为该新类创建“元类”类,这是另一个单例类型的Class
对象。
C函数调用( class.c
)是:
rb_define_class rb_define_class_id rb_class_new(super); rb_make_metaclass(klass, RBASIC(super)->klass);
因此,每次定义一个新类时,Ruby都会定义另一个带有元信息的类。
当你定义一个类方法时,我的意思是, def self.method
,在内部,ruby调用rb_define_singleton_method
。 您可以检查它是否执行以下步骤:
创建一个ruby文件test.rb
:
class A def self.foo end end
并运行以下命令:
ruby --dump insns test.rb
您将获得以下输出:
== disasm: @kcount.rb>=============== 0000 trace 1 ( 70) 0002 putspecialobject 3 0004 putnil 0005 defineclass :A, , 0 0009 leave == disasm: @kcount.rb>============ 0000 trace 2 ( 70) 0002 trace 1 ( 71) 0004 putspecialobject 1 0006 putself 0007 putobject :foo 0009 putiseq foo 0011 opt_send_simple 0013 trace 4 ( 73) 0015 leave ( 71) == disasm: ================== 0000 trace 8 ( 71) 0002 putnil 0003 trace 16 ( 72) 0005 leave
define_singleton_method
映射到rb_obj_define_method
C函数( object.c
),它执行以下调用:
rb_obj_define_method rb_singleton_class(obj) rb_mod_define_method
函数rb_singleton_class
公开了在定义类时创建的元类,但它也为此元类创建了一个新的元类。
根据这个函数的Ruby文档:“如果一个obj是一个类,返回的单例类也有自己的单例类,以保持元类的inheritance结构的一致性”。
这就是为什么在定义类方法时类的数量增加1的原因。
如果您通过以下方式更改代码,则会发生相同的效
class A end A.singleton_class
singleton_class
映射到rb_obj_singleton_class
C函数,该函数调用rb_singleton_class
。
即使您创建了一个类方法并调用了singleton_class
方法,创建的类的数量也不会改变,因为已经创建了处理元信息所需的所有类。 例:
class A def self.foo nil end end A.singleton_class
上面的代码将继续返回3。
第一个是类的本征类。 第二个也与特征类相关,作为方法处理程序:
>> def count_classes >> ObjectSpace.count_objects[:T_CLASS] >> end => nil >> k = count_classes => 890 >> class A; end => nil >> puts count_classes - k 2 # eigenclass created here => nil >> k = count_classes => 892 >> class A; def self.foo; nil; end; end => nil >> puts count_classes - k 1 # A/class eigenclass method handler? => nil >> k = count_classes => 893 >> class A; def bar; nil; end; end => nil >> puts count_classes - k 0 # instance method don't count => nil >> class A; def self.baz; nil; end; end => nil >> puts count_classes - k 0 # A/eigenclass already has a handler => nil >> class B < A; end => nil >> puts count_classes - k 2 # the class and its eigenclass, again => nil >> class C; end => nil >> k = count_classes => 897 >> class C; def foo; end; end => nil >> puts count_classes - k 0 # so... definitely class method related >> class B; def self.xyz; end; end => nil >> puts count_classes - k 1 # B/eigenclass handler => nil >> k = count_classes => 898 >> a = A.new => # >> puts count_classes - k 0 => nil >> def a.zyx; end => nil >> puts count_classes - k 1 # a/eigenclass handler => nil
我不太熟悉ruby internals以确定,但那将是我最好的猜测。
在ObjectSpace
doc页面上 ,请注意句子:“返回的哈希的内容是特定于实现的。将来可能会更改。” 换句话说,使用ObjectSpace.count_objects
你永远不会知道,除非你深入研究特定的Ruby实现。 让我为你演示一下:
def sense_changes prev ObjectSpace.count_objects.merge( prev ) { |_, a, b| a - b }.delete_if { |_, v| v == 0 } end prev = ObjectSpace.count_objects # we do absolutely nothing sense_changes( prev ) #=> { :FREE=>-364, :T_OBJECT=>8, :T_STRING=>270, :T_HASH=>11, :T_DATA=>4, :T_MATCH=>11, :T_NODE=>14}
而你可以继续疑惑,直到奶牛回到家中,在你没有做任何事情的时候,天堂在ObjectSpace
发生了什么。 至于:T_CLASS
字段改变3,你观察到,Denis的答案适用:1是由类本身引起的,1是由它的本征类引起的,1是由我们不知道是什么(更新:正如tlewin所示,它是本征类的本征类)。 让我简单地说,Ruby对象在创建时没有分配的特征类(更新:正如tlewin所示,类是此规则的一个例外。)。
除非您是核心开发人员,否则您几乎不需要怀疑ObjectSpace.count_objects
哈希的内容。 如果您有兴趣通过ObjectSpace
访问类,请使用
ObjectSpace.each_object( Class )
确实:
k = ObjectSpace.each_object( Class ).to_a a = Class.new ObjectSpace.each_object( Class ).to_a.size - k.size #=> 1 ObjectSpace.each_object( Class ).to_a - k == [ a ] #=> true