Метаклассы Ruby: почему три, когда определены одноэлементные методы?

Давайте посчитаем классы в области МРТ:

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

Пожалуйста, объясните мне, почему три?

3 ответа

Решение

Глядя на код МРТ, каждый раз, когда вы создаете 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внутренне, рубиновые звонки 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

Я не достаточно знаком с внутренностями рубина, чтобы знать наверняка, но это было бы моим лучшим предположением.

На 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 пока ты ничего не сделал. Для :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
Другие вопросы по тегам