2012-01-01 4 views
6

У меня есть объект класса, и я хочу его дублировать с помощью dup. Одна из переменных экземпляра - это массив, и, похоже, он ссылается на него. Я думал, что dup фактически создал DUPLICATE.Переменная экземпляра все еще ссылается после «dup»

Вот мой IRB сессии:

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] 

Я ожидал бы a быть неизменным, потому что dup создает совершенно новую переменную, а не ссылаться.

Также обратите внимание, что если вы должны были заменить [1,2,3] на скаляр в G::initialize, dup не будет ссылаться на него.

ответ

6

dup crates a shallow copy; объекты, на которые ссылаются переменные экземпляра, не копируются.

Канонический (например, очень простой) хакерский взлом - это маршал/unmarshal, который может или не может работать в вашей фактической usecase (при условии, что это упрощенный пример). Если это не так, или если сортировка неэффективна, маршрут initialize_copy является лучшим вариантом.

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 
7

Реализация по умолчанию dup и clone просто сделать неполную копию, так что вы будете иметь два объекта, относящиеся к одной и той же матрице. Чтобы получить поведение, которое вы хотите, вы должны определить initialize_copy функцию (которая вызывается dup и clone):

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

Затем эти два объекта будут ссылаться на двух разных массивов. Если массивы имеют изменяемые объекты в них, вы можете пойти еще глубже и dup каждый объект в массивах:

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

Override dup или clone метод:

def dup 
    Marshal::load(Marshal.dump(self)) 
    end