只是为了超级清楚。
这是一个解释这个问题的快速 ruby 脚本:
#!/usr/bin/env ruby
puts ObjectSpace.count_objects[:T_CLASS] #>> 471
class X
def self.foo
end
def bar
end
end
puts ObjectSpace.count_objects[:T_CLASS] #>> 473
这就是 OP 所说的“ObjectSpace.count_objects[:T_CLASS] 将计数增加 2”的含义。我们将这个额外的类称为 X 的单例类,因为这似乎是 Ruby 内部对它的称呼。
irb> X
=> X
irb> X.singleton_class
=> <Class: X>
请注意,#foo
方法是一个实例方法X.singleton_class
, not X
.
irb> X.instance_methods(false)
=> [:baz]
irb> X.singleton_class.instance_methods(false)
=> [:foo]
那么为什么是:foo
存储在X.singleton_class
代替X
?是不是永远只有一个X
?
我认为主要原因是一致性。考虑以下有关普通实例对象的更简单的场景。
car = Car.new
def car.go_forth_and_conquer
end
正如 @mikej 精彩地解释的那样,这个新方法存储在 car 的单例类中。
irb> car.singleton_class.instance_methods(false)
=> [:go_forth_and_conquer]
类是对象
现在,类也是对象。每个类都是一个实例Class
。因此,当一堂课(比如X
) 被定义,ruby 实际上创建了一个实例Class
,然后向实例添加方法(类似于我们所做的car
上面。)例如,这是创建新类的另一种方法
Car = Class.new do
def go_forth_and_conquer
puts "vroom"
end
end
Car.new.go_forth_and_conquer
因此,重用代码并以相同的方式执行会更容易(即保留foo
in X.singleton_class
。)这可能需要更少的努力,并且会导致更少的意外,因此没有人需要编写代码来处理Class
实例与其他实例不同。
可能并不重要
您可能会想,如果 Ruby 没有用于实例的单例类Class
,可能会节省一些内存。然而,在我看来,哪里bar
实际上存储的是我们可能不应该依赖的实现细节。 Rubinius、MRI 和 JRuby 都可以以不同的方式存储方法和实例,只要行为一致即可。据我们所知,Ruby 可能有一个合理的实现,它不会急切地为类对象创建单例类,原因与您概述的完全相同,只要整体行为符合 ruby 规范。 (例如,一个实际的单例类直到#singleton_class
首先调用方法。)