Reputation: 23
I have a class that primarily implements some logic around a multi-dimensional array, which is essentially a grid of numbers. These numbers can swap positions, etc.. However, when they swap, other objects of the same class also appear to be modified. I'm not sure why.
I'm using instance variables to store the grid, so I don't understand why changes are apparently affecting other class members.
Here's a simplified example;
class TestGrid
attr_accessor :grid
@grid = []
def initialize(newgrid)
@grid = newgrid
end
def to_s
out = ""
@grid.each{|row|
out += row.join("|") + "\n"
}
out
end
def swap(x, y)
@grid[x[0]][x[1]], @grid[y[0]][y[1]] = @grid[y[0]][y[1]], @grid[x[0]][x[1]]
end
end
When we interact with a single instance in IRB, things look fine;
1.9.3-p385 :001 > require './TestGrid.rb'
=> true
1.9.3-p385 :002 > x = TestGrid.new([[1,2],[3,4]])
=> 1|2
3|4
1.9.3-p385 :003 > x.swap([0,1],[1,1])
=> [4, 2]
1.9.3-p385 :004 > puts x
1|4
3|2
=> nil
However, if I create a second instance by cloning or duping;
1.9.3-p385 :006 > x = TestGrid.new([[1,2],[3,4]])
=> 1|2
3|4
1.9.3-p385 :007 > y = x.clone
=> 1|2
3|4
1.9.3-p385 :008 > x.swap([0,1],[1,1])
=> [4, 2]
1.9.3-p385 :009 > puts x
1|4
3|2
=> nil
1.9.3-p385 :010 > puts y
1|4
3|2
=> nil
Why are my changes to x also being applied to y? From my understanding of Object#Clone, theses are supposed to be distinct instance, unrelated to each other. Their object ID's would seem to support that expectation;
1.9.3-p385 :012 > puts "#{x.object_id} #{y.object_id}"
70124426240320 70124426232820
For reference, I ended up creating an initialize_copy method which ensures the affected parameter is deep copied. I didn't really like the idea of Marshalling objects around just to copy an array deeply, so I decided on this instead.
def initialize_copy(original)
super
@grid = []
original.grid.each{|inner|
@grid << inner.dup
}
end
Upvotes: 2
Views: 274
Reputation: 61520
By default, dup
and clone
produce shallow copies of the objects they are invoked on. Meaning that x
and y
in your example still reference the same area of memory.
http://ruby-doc.org/core-2.0/Object.html#method-i-dup
http://ruby-doc.org/core-2.0/Object.html#method-i-clone
You can override them inside of your customized class to produce a deep copy in a different area of memory.
A common idiom in Ruby is to use the Marshal#load
and Marshal#dump
methods of the Object superclass to produce deep copies. (Note: these methods are normally used to serialize/deserialze objects).
def dup
new_grid = Marshal.load( Marshal.dump(@grid) )
new_grid
end
irb(main):007:0> x = TestGrid.new([[1,2],[3,4]])
=> 1|2
3|4
irb(main):008:0> y = x.dup
=> [[1, 2], [3, 4]]
irb(main):009:0> x.swap([0,1],[1,1])
=> [4, 2]
irb(main):010:0> puts x
1|4
3|2
=> nil
irb(main):011:0> y
=> [[1, 2], [3, 4]]
irb(main):012:0> puts y
1
2
3
4
=> nil
irb(main):013:0>
y
remains the same after the swap.
Alternatively, create a new array, iterate through @grid and push its subarrays into the array.
def dup
new_grid = []
@grid.each do |g|
new_grid << g
end
new_grid
end
Upvotes: 2