How do I get the class of a BasicObject instance?

后端 未结 8 2034
小鲜肉
小鲜肉 2020-12-08 03:03

I have a script that iterates using ObjectSpace#each_object with no args. Then it prints how many instances exist for each class.

I realized that some

相关标签:
8条回答
  • 2020-12-08 03:25

    If you can upgrade to Ruby 2.0, you don't need to implement anything at all:

    >> Kernel.instance_method(:class).bind(BasicObject.new).call
    => BasicObject
    
    0 讨论(0)
  • 2020-12-08 03:26

    I don't know about doing it in Ruby, but this is straightforward using the C API to Ruby. The RubyInline Gem makes adding bits of C to your Ruby code quite easy:

    require 'inline'
    class Example
      inline do |builder|  
        builder.c_raw_singleton <<SRC, :arity => 1
          VALUE true_class(VALUE self, VALUE to_test) {
            return rb_obj_class(to_test);
          }
    SRC
       end
    end
    

    And then:

    1.9.2p180 :033 > Example.true_class(20.minutes)
     => ActiveSupport::Duration 
    
    0 讨论(0)
  • 2020-12-08 03:27

    The following code creates a BasicKernel module via duplication of the Kernel module and subsequent removal of all methods except the class method. The BasicKernel is included into the BasicObject class (just like Kernel is included into Object).

    In req_methods, you can specify arbitrary subset of Kernel methods to be preserved.

    class BasicObject
      include ::BasicKernel = ::Kernel.dup.module_eval {
        v = $VERBOSE
        $VERBOSE = nil               # suppress object_id warning
        req_methods = [:class]       # required methods (to be preserved)
        all_methods = public_instance_methods +
                   protected_instance_methods +
                     private_instance_methods
        all_methods.each { |x| remove_method(x) unless req_methods.include?(x) }
        $VERBOSE = v
        self
      }
    end
    
    # tests:
    puts RUBY_VERSION               # 1.9.2
    class B < BasicObject; end
    class X;               end
    p BasicObject.new.class           # BasicObject
    p B          .new.class           # B
    p X          .new.class           # X
    p B.instance_method(:class).owner # BasicKernel
    p X.instance_method(:class).owner # Kernel
    p Object.ancestors                # [Object, Kernel, BasicObject, BasicKernel]
    p BasicKernel.instance_methods    # [:class]
    

    Edit: See the Note in https://stackoverflow.com/a/10216927/641718

    0 讨论(0)
  • 2020-12-08 03:28

    The following solution refers to the superclass of the eigenclass. As a consequence, it has the side effect of allocating the eigenclass (detectable by ObjectSpace.count_objects[:T_CLASS] in MRI). But since BasicObject#class is only invoked on blank slate objects (i.e. objects that are not kind-of Object, i.e. that are not Objects) the side effect also applies just for blank slate objects. For Objects, the standard Kernel#class is invoked.

    class BasicObject
      def class
        (class << self; self end).superclass
      end
    end
    
    # tests:
    puts RUBY_VERSION               # 1.9.2
    class B < BasicObject; end
    class X;               end
    p BasicObject.new.class             # BasicObject
    p B          .new.class             # B
    p X          .new.class             # X
    p               6.class             # Fixnum
    p B.instance_method(:class).owner   # BasicObject
    p X.instance_method(:class).owner   # Kernel
    p          6.method(:class).owner   # Kernel
    

    Edit - Note: Indeed, there is an issue with ActiveSupport::Duration. This class uses interception (method_missing) for redirecting messages to the :value attribute. As a consequence, it provides false introspection for its instances. To preserve this falsity, it is necessary to use another name for the class map, e.g. the proposed __realclass__. Thus, the modified solution might look like this:

    class BasicObject
      def __realclass__; (class << self; self end).superclass end
    end
    class Object; alias __realclass__ class end
    

    Another way of not invoking class << self on Objects is via Module#===, as suggested by Kelvin on this page.

    0 讨论(0)
  • 2020-12-08 03:31

    fguillen's link made me think of this way.

    Pros:

    1. It doesn't need external libraries.

    Cons:

    1. It must be executed before loading any classes that subclass BasicObject.
    2. It adds a method to every new class

    .

    class BasicObject
      def self.inherited(klass)
        klass.send(:define_method, :__realclass__) { klass }
      end
      def __realclass__
        BasicObject
      end
    end
    
    # ensures that every Object will also have this method
    class Object
      def __realclass__
        Object.instance_method(:class).bind(self).call
      end
    end
    
    require 'active_support/core_ext'
    
    20.seconds.__realclass__  # => ActiveSupport::Duration
    
    # this doesn't raise errors, so it looks like all objects respond to our method
    ObjectSpace.each_object{|e| e.__realclass__ }
    
    0 讨论(0)
  • 2020-12-08 03:32

    This is my modification of @paon's answer:

    Reasoning behind the changes:

    • Method name doesn't clash with existing libs, e.g. the ActiveSupport::Duration instance behavior 2.seconds.class remains Fixnum.
    • Since Object doesn't have its own __realclass__ method, we want to avoid allocating the eigenclass for those instances. @paon's original answer did this inherently by defining the class method name.

    class BasicObject
      def __realclass__
        ::Object === self ?
          # Note: to be paranoid about Object instances, we could 
          # use Object.instance_method(:class).bind(s).call.
          self.class :
          (class << self; self end).superclass
      end
    end
    
    # test
    require 'active_support/core_ext/integer'
    require 'active_support/core_ext/numeric'
    
    duration = 2.seconds
    string = 'hello world'
    p duration.class  # => Fixnum
    p string.class    # => String
    GC.start
    p ObjectSpace.count_objects[:T_CLASS]  # => 566
    
    # creates the eigenclass
    p duration.__realclass__  # => ActiveSupport::Duration
    p ObjectSpace.count_objects[:T_CLASS]  # => 567
    
    # doesn't create the eigenclass
    p string.__realclass__  # => String
    p ObjectSpace.count_objects[:T_CLASS]  # => 567
    
    0 讨论(0)
提交回复
热议问题