在MRI Ruby中,何时分配单例类?
我刚刚发现单例类也可以有自己的单例类。
因此,可以调用
Object.singleton_class.singleton_class.singleton_class.singleton_class...
直到时间本身的尽头。显然,默认情况下,虚拟机不会分配所有这些单一实例类。
Ruby 虚拟机何时分配单一实例类?如果我不在其上定义任何方法并且不调用Object#singleton_class
,则对象是否具有单例类?
从 Ruby 程序的角度来看,每个对象总是有一个单例类。 包括类。包括单例类。
但是,正如您所猜测的,某些 Ruby 实现通过不提前分配所有单例类来优化这一点。
以下是YARV所做的(据我所知(:
- 单例类是懒惰分配的。仅当您尝试访问或打开它,或者在其中定义方法、常量或变量时,才会创建它们。这是内存优化。
- 但是,这种延迟分配有一些开销。由于模块和类通常具有单例方法,因此对于类和模块,这是不同的:对于类和模块,YARV 会在实例化(类/模块(对象后立即创建一个单例类。这是一种速度优化。
- 当然,正如您在问题中所说,这会导致无限递归。因此,#2 仅适用于"普通"类,不适用于单例类。
所以:单例类是懒惰地实例化的。类和模块除外。异常中的例外是单例类。
但是,让我重复一下我在开头写的内容:这是 YARV 的私有内部实现细节。其他 Ruby 实现的行为可能相同,也可能不同。
在Ruby中,每个对象始终存在一个单例类,包括单例类。
TL;DR根据我的实验,至少对于 YARV 2.6.5,所有单例类都是延迟分配的。
实验前
ObjectSpace.each_object(type)
返回一个枚举器,该枚举器遍历所有.is_a?(type)
的对象,只要它还没有被垃圾回收。
实验 1:定义空类
打开 irb,然后执行以下步骤。请注意,计算机上对象的绝对数量可能会有所不同,但差异很重要。
ObjectSpace.each_object(Class).count #=> 649
class A; end
ObjectSpace.each_object(Class).count #=> 650
定义空类仅分配 1 个类对象,因此不会分配单例类。
实验 2:定义仅包含实例方法的类
class B
def foo; end
def bar; end
end
ObjectSpace.each_object(Class).count #=> 651
创建此类类也不会创建单例类。
实验 3:定义包含类方法的类
class C
def self.foo; end
end
ObjectSpace.each_object(Class).count #=> 653
这次,创建了 2 个类,一个是类C
本身,另一个是C
的单例类。
实验4:新对象
a = A.new
ObjectSpace.each_object(Class).count #=> 653
因此,实例化不会创建单例类。
实验 5:添加单例方法
def a.foo; end
ObjectSpace.each_object(Class).count #=> 654
正如预期的那样,添加单一实例方法确实会创建单一实例类。
实验6:继承
class D; end
class E < D
def self.foo; end
end
ObjectSpace.each_object(Class).count #=> 658
这次,分配了 4 个类,其中 2 个是"普通"类,另外 2 个是单例类。因为我们证明了创建空类不会动态分配单例类,所以在E
中定义单例方法时必须分配D
的单例类。因此,结论是,当必须创建单例类时,其父类(也是单例类(必须已经存在。如果没有,则会动态创建。
您可以自己进行模块的实验。