shallow copy と deep copy

こんにちは!kossyです!




さて、今回はshallow copydeep copy の違いについて、
ブログに残してみたいと思います。
なお、例はRubyで書いていきます。



shallow copy

しっくりくる説明を引用させて頂きました。

シャローコピー(浅いコピー)はプリミティブ値(文字列、数値、真偽値、null、undefined、Symbol)をコピーするが、
それ以外のオブジェクトは参照をコピーする。参照がコピーされるということは、コピー元とコピー先でオブジェクトが共有されるということである。
一方、ディープコピー(深いコピー)はプリミティブ値だけでなく、オブジェクトも値としてコピーする。したがって、コピー元とコピー先のオブジェクトは別物である。

出典: https://nansystem.com/shallow-copy-vs-deep-copy/

この挙動をRubyのコードで試してみます。

Rubyには、shallow copyを実現するメソッドが2つあります。

# dup

irb(main):001:0> str = 'base'
=> "base"
irb(main):002:0> dupped_str = str.dup
=> "base"
irb(main):003:0> str == dupped_str
=> true
irb(main):004:0> str === dupped_str
=> true
irb(main):005:0> str.object_id == dupped_str.object_id
=> false
irb(main):006:0> str.object_id
=> 70127702447420
irb(main):007:0> dupped_str.object_id
=> 70127702479340



# clone

irb(main):001:0> str = 'base'
=> "base"
irb(main):002:0> cloned_str = str.clone
=> "base"
irb(main):003:0> str == clone_str
=> true
irb(main):004:0> str === clone_str
=> true
irb(main):005:0> str.object_id == cloned_str.object_id
=> false
irb(main):006:0> str.object_id
=> 70203614857500
irb(main):007:0> cloned_str.object_id
=> 70203610561780

文字列をコピーする場合は、異なるオブジェクト(参照するメモリ領域も異なるもの)として定義しています。


次に、Arrayを例にしてみます。

irb(main):001:0> arr = ['one', 'two']
=> ["one", "two"]
irb(main):002:0> dupped_arr = arr.dup
=> ["one", "two"]
irb(main):003:0> dupped_arr.first.upcase!
=> "ONE"
irb(main):004:0> arr
=> ["ONE", "two"] <= !!!
irb(main):005:0> dupped_arr
=> ["ONE", "two"]
irb(main):006:0> arr.object_id
=> 70260964042220
irb(main):007:0> dupped_arr.object_id
=> 70260967711360


irb(main):001:0> arr = ['one', 'two']
=> ["one", "two"]
irb(main):002:0> cloned_arr = arr.clone
=> ["one", "two"]
irb(main):003:0> cloned_arr.first.upcase!
=> "ONE"
irb(main):004:0> arr
=> ["ONE", "two"]
irb(main):005:0> cloned_arr
=> ["ONE", "two"]
irb(main):006:0> arr.object_id
=> 70296824577020
irb(main):007:0> cloned_arr.object_id
=> 70296816415960


なぜこのような挙動になるのかは

に書いてありました。

clone や dup はオブジェクト自身を複製するだけで、オブジェクトの指している先(たとえば配列の要素など)までは複製しません。

出典: https://docs.ruby-lang.org/ja/latest/method/Object/i/clone.html

なるほど、だから破壊的変更がarrにも反映されてしまっていたのですね、、、



deep copy

deep copyshallow copyとはオブジェクトの指している先までコピーする点で異なります。

Rubydeep copyを実現する場合、Marshalクラスを用いることで実現できますが、
Railsを使って開発をしている場合は、deep_dupメソッドを用いることでも実現ができます。

[1] pry(main)> arr = ['one', 'two']
=> ["one", "two"]
[2] pry(main)> deep_dupped_arr = arr.deep_dup
=> ["one", "two"]
[3] pry(main)> deep_dupped_arr.first.upcase!
=> "ONE"
[4] pry(main)> arr
=> ["one", "two"]
[5] pry(main)> deep_dupped_arr
=> ["ONE", "two"]
[6] pry(main)> arr.object_id
=> 70258270856240
[7] pry(main)> deep_dupped_arr.object_id
=> 70258261330960

deep_dupして生成したオブジェクトがコピー元のオブジェクトに影響を与えていないことが確認できました。




勉強になりました。