【问题标题】:When is a singleton class allocated in Ruby?何时在 Ruby 中分配单例类?
【发布时间】:2019-12-08 21:02:05
【问题描述】:

在 MRI Ruby 中,何时分配单例类?

我刚刚发现单例类也可以有自己的单例类。

因此,可以调用

Object.singleton_class.singleton_class.singleton_class.singleton_class...

直到时间结束。显然,默认情况下,虚拟机不会分配所有这些单例类。

Ruby 虚拟机何时分配单例类?如果我没有在其上定义任何方法并且不调用Object#singleton_class,那么对象是否有一个单例类?

【问题讨论】:

  • 这不只是一个实现问题吗?在创建对象时创建单例类或首次引用对象的单例类是否重要?

标签: ruby singleton


【解决方案1】:

从 Ruby 程序的角度来看,每个对象总是有一个单例类。包括班级。包括单例类。

但是,正如您所猜测的,一些 Ruby 实现通过不预先分配所有单例类来优化这一点。

这是 YARV 所做的(据我所知):

  1. 单例类被延迟分配。它们仅在您尝试访问它、打开它或在其中定义方法或常量或变量时创建。这是一个内存优化
  2. 但是,这种惰性分配有一些开销。由于模块和类通常有单例方法,对于类和模块来说,这是不同的:对于类和模块,YARV 在(类/模块)对象被实例化后立即创建一个单例类。这是一个速度优化
  3. 当然,正如您在问题中所说,这会导致无限递归。因此,#2 仅适用于“普通”类,而不适用于单例类。

所以:单例类是惰性实例化的。类和模块除外。异常中的异常是单例类。

但是,让我重复一下我在开头写的内容:这是 YARV 的私有内部实现细节。其他 Ruby 实现的行为可能相同,也可能不同。

Ruby 中,每个对象都始终存在一个单例类,包括单例类。

【讨论】:

  • AFAIK,当一个类包含一个模块时,该模块被插入到该类的祖先链中,但是模块的单例类没有插入到该类的单例类的祖先链中,为什么?是不是跟单例类的惰性分配策略有关?
  • YARV 2.6.5 似乎不遵守优化规则。当我在新的 irb 中运行 ObjectSpace.each_object(Class).count 时,它返回 652。当我在定义类 A 后运行这个表达式时,它返回 653。当我在运行 A.singleton_class 后再次运行它时,它返回 654。同样模块的行为。所以看起来单例类都是懒惰分配的。
  • "是否与单例类的惰性分配策略有关" - 不可能与此有关,因为它是一种优化,不允许优化改变语言的语义。 (否则,它们不会是优化。)这就是 Module#append_features 的默认语义是如何定义的。
  • "YARV 2.6.5 似乎没有遵守优化规则。" - 有趣的。那可能是我当时正在阅读的一篇关于 MRI 的文章。
【解决方案2】:

TL;DR 根据我的实验,至少对于 YARV 2.6.5,所有单例类都是延迟分配的。

实验前

  • ObjectSpace.each_object(type) 返回一个枚举器,它遍历 .is_a?(type) 的所有对象,只要它还没有被垃圾回收。

实验一:定义一个空类

打开一个 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 的单例类。所以结论是,当必须创建单例类时,它的父类(也是单例类)必须已经存在。如果没有,它是动态创建的。

你可以自己做模块的实验。

【讨论】:

    猜你喜欢
    • 2018-12-10
    • 2011-11-26
    • 1970-01-01
    • 1970-01-01
    • 2020-08-05
    • 1970-01-01
    • 1970-01-01
    • 2013-10-28
    • 1970-01-01
    相关资源
    最近更新 更多