Ruby元类:为什么定义了三个单例方法?

Ruby元类:为什么定义了三个单例方法?,ruby,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 请解释一下,为什么是三个?第一个是类的本征类。第二个也与本征类相关,作为一个方法处理程序: >> def count_c

让我们计算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

请解释一下,为什么是三个?

第一个是类的本征类。第二个也与本征类相关,作为一个方法处理程序:

>> 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
=> #<A:0x007f810c112350>
>> puts count_classes - k
0
=> nil
>> def a.zyx; end
=> nil
>> puts count_classes - k
1                                         # a/eigenclass handler
=> nil
>定义计数\u类
>>ObjectSpace.count_对象[:T_类]
>>结束
=>零
>>k=计数_类
=> 890
>>甲级;结束
=>零
>>将计数设置为-k
2#此处创建的特征类
=>零
>>k=计数_类
=> 892
>>甲级;def self.foo;无结束;结束
=>零
>>将计数设置为-k
1#A/类特征类方法处理程序?
=>零
>>k=计数_类
=> 893
>>甲级;def棒;无结束;结束
=>零
>>将计数设置为-k
0#实例方法不计数
=>零
>>甲级;def self.baz;无结束;结束
=>零
>>将计数设置为-k
0#一个/特征类已经有一个处理程序
=>零
>>B级零
>>将计数设置为-k
2#类及其本征类
=>零
>>丙级;;结束
=>零
>>k=计数_类
=> 897
>>丙级;;德福;结束;结束
=>零
>>将计数设置为-k
所以。。。绝对与类方法相关
>>乙级;;def self.xyz;结束;结束
=>零
>>将计数设置为-k
1#B/特征类处理程序
=>零
>>k=计数_类
=> 898
>>新的
=> #
>>将计数设置为-k
0
=>零
>>def a.zyx;结束
=>零
>>将计数设置为-k
1#a/特征类处理程序
=>零

我对ruby的内部结构还不太熟悉,所以无法确定,但这是我最好的猜测。

看看MRI代码,每次你创建一个
类,在ruby中它是
类型的对象,ruby会自动为这个新类创建“元类”类,这是singleton类型的另一个
对象

C函数调用(
class.C
)是:

因此,每次定义一个新类时,Ruby都会用元信息定义另一个类

当您定义一个类方法时,我的意思是,
def self.method
,ruby在内部调用
rb\u define\u singleton\u method
。您可以通过以下步骤进行检查:

创建一个ruby文件
test.rb

class A
  def self.foo
  end
end
并运行以下命令:

ruby --dump insns test.rb
您将获得以下输出:

== disasm: <RubyVM::InstructionSequence:<main>@kcount.rb>===============
0000 trace            1                                               (  70)
0002 putspecialobject 3
0004 putnil
0005 defineclass      :A, <class:A>, 0
0009 leave
== disasm: <RubyVM::InstructionSequence:<class:A>@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  <callinfo!mid:core#define_singleton_method, argc:3, ARGS_SKIP>
0013 trace            4                                               (  73)
0015 leave                                                            (  71)
== disasm: <RubyVM::InstructionSequence:foo@kcount.rb>==================
0000 trace            8                                               (  71)
0002 putnil
0003 trace            16                                              (  72)
0005 leave
函数
rb\u singleton\u class
公开了定义类时创建的元类,但它也为该元类创建了一个新的元类

根据这个函数的Ruby文档:“如果obj是一个类,那么返回的singleton类也有自己的singleton类,以保持元类继承结构的一致性”

这就是定义类方法时类的数量增加1的原因

如果通过以下方式更改代码,也会产生相同的效果:

class A
end
A.singleton_class
singleton_类
映射到
rb_obj_singleton_类
C函数,该函数调用
rb_singleton_类

即使创建一个类方法并调用
singleton\u class
方法,创建的类的数量也不会改变,因为处理元信息所需的所有类都已创建。例如:

class A
  def self.foo
    nil
  end
end

A.singleton_class
上面的代码将继续返回3。

最后,请注意这句话:“返回的哈希的内容是特定于实现的。将来可能会更改。”换句话说,对于
ObjectSpace.count\u 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\u objects
hash的内容感到好奇。如果您对通过
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

有趣的问题。在我(非常,非常有限)理解MRI ruby的C对象模型时,可能是:类A,类A的特征类;类的特征类?据我理解,A的本征类的“超类”应该是类的本征类;据我所知,本征类是在需要的时候才被创建的,所以也许类的本征类只有在创建第一个类的时候才被创建。试两次测试,看看它是否仍然输出三个?立即创建类的特征类,因为几乎每个类都至少有一个“类方法”。如果您知道必须创建它们,那么懒散地创建它们是没有意义的。我刚刚发布了一个答案(我刚刚删除了该答案),它有效地复制了此交换。FWIW,在上面的示例中,如果您在定义类时没有定义类方法,类的数量增量只有2而不是3。@m_x问题是,如果您创建另一个类APrime,类的数量将再次增加3,而不是2。@PeterAlfvin:它似乎与类方法处理程序有关(请参阅我发布的irb转储文件)哇!我对ruby对象模型的所有想法都是错误的。元类和单例类之间的区别是什么?我一直以为是一样的。谢谢你,特莱温!但在文献中,总是写元类和sinlgeton类(和本征类)是相同的。这是必要的
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