こんにちは!kossyです!
さて、今回はshallow copy と deep copy の違いについて、
ブログに残してみたいと思います。
なお、例はRubyで書いていきます。
shallow copy
しっくりくる説明を引用させて頂きました。
シャローコピー(浅いコピー)はプリミティブ値(文字列、数値、真偽値、null、undefined、Symbol)をコピーするが、
それ以外のオブジェクトは参照をコピーする。参照がコピーされるということは、コピー元とコピー先でオブジェクトが共有されるということである。
一方、ディープコピー(深いコピー)はプリミティブ値だけでなく、オブジェクトも値としてコピーする。したがって、コピー元とコピー先のオブジェクトは別物である。
この挙動を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 copyはshallow copyとはオブジェクトの指している先までコピーする点で異なります。
Rubyでdeep 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して生成したオブジェクトがコピー元のオブジェクトに影響を与えていないことが確認できました。
勉強になりました。