Question

I have an object of a class, and I want to duplicate it with dup. One of the instance variables is an array, and it seems to be referencing it. I thought dup actually created a DUPLICATE.

Here's my IRB session:

irb(main):094:0> class G
irb(main):095:1> attr_accessor :iv
irb(main):096:1> def initialize
irb(main):097:2> @iv = [1,2,3]
irb(main):098:2> end
irb(main):099:1> end
=> nil

irb(main):100:0> a=G.new
=> #<G:0x27331f8 @iv=[1, 2, 3]>

irb(main):101:0> b=a.dup
=> #<G:0x20e4730 @iv=[1, 2, 3]>

irb(main):103:0> b.iv<<4
=> [1, 2, 3, 4]
irb(main):104:0> a
=> #<G:0x27331f8 @iv=[1, 2, 3, 4]

I would expect a to be unchanged, because dup creates a whole new variable, not reference.

Also note that if you were to replace [1,2,3] with a scalar in G::initialize, dup will not reference it.

Was it helpful?

Solution

dup crates a shallow copy; the objects referred to by instance variables are not copied.

The canonical (e.g., Really Easy) deep copy hack is to marshal/unmarshal, which may or may not work in your actual usecase (assuming this is a simplified example). If it doesn't, or if marshalling is to inefficient, the initialize_copy route is a better option.

pry(main)> a = G.new
=> #<G:0x9285628 @iv=[1, 2, 3]>
pry(main)> b = a.dup
=> #<G:0x92510a8 @iv=[1, 2, 3]>
pry(main)> a.iv.__id__
=> 76819210
pry(main)> b.iv.__id__
=> 76819210
pry(main)> b = Marshal::load(Marshal.dump(a))
=> #<G:0x9153c3c @iv=[1, 2, 3]>
pry(main)> a.__id__
=> 76819220
pry(main)> b.__id__
=> 76193310

OTHER TIPS

The default implementation of dup and clone just make a shallow copy, so you will have two objects referring to the same array. To get the behavior that you want, you should define an initialize_copy function (which is called by dup and clone):

class G
  attr_accessor :iv
  def initialize_copy(source)
    super
    @iv = source.iv.dup
  end
end

Then the two objects will refer to two different arrays. If the arrays have mutable objects in them, you might want to go even deeper and dup each object in the arrays:

def initialize_copy(source)
  super
  @iv = source.iv.collect &:dup
end

Override dup or clone method:

  def dup
    Marshal::load(Marshal.dump(self))
  end
Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top