Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Instance variable still references after 'dup'

Tags:

ruby

dup

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.

like image 567
itdoesntwork Avatar asked Jan 01 '12 02:01

itdoesntwork


2 Answers

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
like image 192
David Grayson Avatar answered Nov 15 '22 03:11

David Grayson


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
like image 23
Dave Newton Avatar answered Nov 15 '22 03:11

Dave Newton