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

时间:2013-11-08 16:44:49

标签: 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

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

3 个答案:

答案 0 :(得分:4)

查看MRI代码,每次在Ruby中创建一个Class类型为Class的对象时,ruby会自动为该新类创建“元类”类,这是另一个{{1单例类型的对象。

C函数调用(Class)是:

class.c

因此,每次定义一个新类时,Ruby都会定义另一个带有元信息的类。

当你定义一个类方法时,我的意思是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

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 映射到define_singleton_method C函数(rb_obj_define_method),后续调用:

object.c

函数 rb_obj_define_method rb_singleton_class(obj) rb_mod_define_method 公开了在定义类时创建的元类,但它也为这个元类创建了一个新的元类。

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

这就是为什么在定义类方法时类的数量增加1的原因。

如果您通过以下方式更改代码,则会发生同样的效果:

rb_singleton_class

class A end A.singleton_class 已映射到singleton_class C函数,该函数调用rb_obj_singleton_class

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

singleton_class

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

答案 1 :(得分:2)

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

>> 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

我不太熟悉红宝石内部以确定,但这是我最好的猜测。

答案 2 :(得分:2)

ObjectSpace doc page,请注意句子:“返回的哈希的内容是特定于实现的。将来可能会更改。”换句话说,除非你深入研究特定的Ruby实现,否则你永远不会知道ObjectSpace.count_objects。让我为你演示一下:

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