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