5

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

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

4

3 回答 3

4

查看 MRI 代码,每次Class在 Ruby 中创建 which is object of typeClass时,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。您可以按照以下步骤检查它:

创建一个红宝石文件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映射到rb_obj_define_methodC 函数 ( ),该函数object.c执行以下调用:

 rb_obj_define_method
   rb_singleton_class(obj)
   rb_mod_define_method

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

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

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

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

class A
end
A.singleton_class

singleton_class被映射到C 函数,该rb_obj_singleton_class函数调用rb_singleton_class.

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

class A
  def self.foo
    nil
  end
end

A.singleton_class

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

于 2013-11-08T22:51:58.297 回答
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

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

于 2013-11-08T19:52:07.520 回答
2

ObjectSpacedoc page中,请注意以下语句:“返回的哈希的内容是特定于实现的。将来可能会更改。” 换句话说,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,丹尼斯的回答适用:1 是由类本身引起的,1 是由其特征类引起的,1 是由我们不知道是什么引起的(更新:正如 tlewin 所示,它是本征类的特征类)。让我补充一点,Ruby 对象在创建时没有分配特征类(更新:正如 tlewin 所示,类是该规则的一个例外。)。

除非你是核心开发者,否则你几乎不需要怀疑ObjectSpace.count_objectshash 的内容。如果您有兴趣通过 访问课程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
于 2013-11-09T05:57:38.310 回答