【问题标题】:Ruby metaclasses: why three when defined singleton methods?Ruby 元类:为什么定义了三个单例方法?
【发布时间】:2013-11-20 18:30:10
【问题描述】:

让我们计算 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 ruby​​ 的 C 对象模型的(非常非常有限的)理解中,可能是:A 类,A 类的 eigenclass ;类 类 ' eigenclass ?据我了解,A的本征类的“超类”应该是类的本征类;据我了解,只有在需要它们时才会创建特征类,所以也许只有在创建第一个类时才会创建类的特征类。试试你的测试两次,看看它是否仍然输出三个?
  • 类的特征类是立即创建的,因为几乎每个类都至少有一个“类方法”。如果您知道无论如何都必须创建它们,那么懒惰地创建它们是没有意义的。
  • 我刚刚发布了一个有效地复制了这个交流的答案(我刚刚删除了它)。 FWIW,在上面的例子中,如果你在定义类的时候没有定义类方法,那么类数的增量只有2而不是3。
  • @m_x 问题是,如果你创建另一个类 APrime,类的数量会再次增加 3,而不是 2。
  • @PeterAlfvin:它似乎与类方法处理程序有关(请参阅我发布的 irb 转储)。

标签: ruby


【解决方案1】:

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

>> 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​​ 内部结构不够熟悉,无法确定,但这是我最好的猜测。

【讨论】:

    【解决方案2】:

    查看 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: <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_method C 函数 (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 映射到rb_obj_singleton_class C 函数,该函数调用rb_singleton_class

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

    class A
      def self.foo
        nil
      end
    end
    
    A.singleton_class
    

    上面的代码会一直返回3。

    【讨论】:

    • 哇。我对 ruby​​ 对象模型的所有想法都是错误的。元类和单例类有什么区别?我一直认为这是同一件事。
    • 谢谢你,特莱温!但是在文献中,总是写元类和单子类(和特征类)是一样的。需要开源 rb_define_singleton_method C 函数。
    • @m_x 我更新了帖子内容来阐明这个元类和单例类的问题。
    • 我认为这是正确的答案。
    【解决方案3】:

    ObjectSpace doc 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_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
    

    【讨论】:

    • 你介绍的很到位……!
    • 很高兴在这里见到你 :-)
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2019-02-01
    • 1970-01-01
    • 2018-06-28
    • 2022-01-13
    • 2023-03-29
    • 2014-09-03
    • 1970-01-01
    相关资源
    最近更新 更多