让我们计算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 中是 Class
类型的对象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。
第一个是类的特征类。第二个也与特征类相关,作为方法处理程序:
>> 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 内部不够熟悉,无法确定,但这将是我最好的猜测。
在ObjectSpace
文档页面上,请注意句子:"返回的哈希的内容是特定于实现的。将来可能会改变。换句话说,对于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
发生了什么,而你什么也没做。至于你观察到的 3 的:T_CLASS
场变化,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