Ruby: object deep copying

2019-06-25 09:43发布

问题:

I'm looking at some techniques to deep-copy objects in Ruby (MRI 1.9.3).
I came across the following example, but I'm not sure about the #dup method implementation. I tested it and it does work, but I don't understand the logical steps of the method, thus I'm not confortable using it in my own code.

Is the statement @name = @name.dup referring to the iVar inside the copy? How? I can't see it.

Could anyone explain it, please?
Also, are there better ways?

class MyClass
  attr_accessor :name

  def initialize(arg_str)   # called on MyClass.new("string")
    @name = arg_str         # initialize an instance variable
  end

  def dup
    the_copy = super        # shallow copy calling Object.dup
    @name = @name.dup       # new copy of istance variable
    return the_copy         # return the copied object
  end
end

回答1:

This is a really thin, very specific implementation of a "deep copy". What it's demonstrating is creating an independent @name instance variable in the clone so that modifying the name of one with an in-place operation won't have the side-effect of changing the clone.

Normally deep-copy operations are important for things like nested arrays or hashes, but they're also applicable to any object with attributes that refer to things of that sort.

In your case, to make an object with a more robust dup method, you should call dup on each of the attributes in question, but I think this example is a bit broken. What it does is replace the @name in the original with a copy, which may break any references you have.

A better version is:

def dup
  copy = super
  copy.make_independent!
  copy
end

def make_independent!
  instance_variables.each do |var|
    value = instance_variable_get(var)

    if (value.respond_to?(:dup))
      instance_variable_set(var, value.dup)
    end
  end
end

This should have the effect of duplicating any instance variables which support the dup method. This skips things like numbers, booleans, and nil which can't be duplicated.



回答2:

When you do super, the instance method @name will be shared between the original and the duplicate. By doing @name = @name.dup, The original gets a new instance of a string as @name, ending up having a different @name from the one that the duplicated instance has.