書籍Effective Rubyを読み進めて浅い(shallow)コピーと深い(deep)コピーというものを学びましたので、簡単にまとめます。
参照と複製について
Rubyでオブジェクトを複製する場合は参照と複製の二つがあります。
それぞれの使い方により、後々に思いもよらぬ動きになるので使い分けておくことが必要です。
参照について
参照は1つのオブジェクトをもとに参照して、代入する動きです。
この場合は=
を使用します。
a = ["Alice","Bob","Jack"]
b = a
p a # => ["Alice","Bob","Jack"]
p b # => ["Alice","Bob","Jack"]
p a.object_id # => 47242730754220
p b.object_id # => 47242730754220
b[0] = "Rian"
p b # => ["Rian", "Bob", "Jack"]
p a # => ["Rian", "Bob", "Jack"]
p a.object_id # => 47242730754220
p b.object_id # => 47242730754220
a
の配列に=
をbを代入すると同じ配列が出力されます。
object_id
メソッドで確認すると同じオブジェクトIDであることが確認できます。
参照されたb
の配列にRian
という新しいオブジェクトを追加した場合、参照元のa
の配列が変化しているのがわかります。
この時のオブジェクトIDも変わりありません。
同じオブジェクトを作るという点では良い影響がありますが、参照元の他のオブジェクトにも反映されてしまうことに注意が必要です。
複製について
他のオブジェクトへの影響を回避する方法を考えてみます。
これは複製
という方法で同じオブジェクトを作る必要があります。
複製
はdupメソッドかcloneメソッドを使います。
a = ["Alice","Bob","Jack"]
b = a.dup
p a # => ["Alice","Bob","Jack"]
p b # => ["Alice","Bob","Jack"]
p a.object_id # => 47067846268240
p b.object_id # => 47067846268200
b[0] = "Rian"
p b # => ["Rian", "Bob", "Jack"]
p a # => ["Alice", "Bob", "Jack"]
p a.object_id # => 47067846268240
p b.object_id # => 47067846268200
dupメソッド
を使った場合、オブジェクトIDが違うことがわかります。
これは、a
の変数に代入された配列と同じものがbに代入されていますが、新しい同じ配列を代入していることがわかります。
このため、b
の変数に新しいオブジェクトを追加しても、元のaの変数には影響がないことがわかります。
この挙動はcloneメソッド
も同じです。
浅いコピー(shallow copy)
先ほどの例をもう少し深ぼりしてみます。
dupメソッドで複製した配列の要素のオブジェクトIDを見てみると、実は同じオブジェクトIDを出力します。
a = ["Alice","Bob","Jack"]
b = a.dup
p a # => ["Alice","Bob","Jack"]
p b # => ["Alice","Bob","Jack"]
p a.object_id # => 47067846268240
p b.object_id # => 47067846268200
b[0] = "Rian"
p b # => ["Rian", "Bob", "Jack"]
p a # => ["Alice", "Bob", "Jack"]
p b[1].object_id # => 47240279609360
p a[1].object_id # => 47240279609360
これは、オブジェクト自身を複製しているだけで、参照先までは複製できません。
これを浅いコピー(shallow copy)と言います。
dupメソッド
とcloneメソッド
の違いは、cloneで複製された場合にfreeze、特異メソッドなどの情報も含めた複製を作成します。
以下はfreezeを指定した場合です。
a = ["Alice","Bob","Jack"]
a.freeze
b = a.clone
c = a.dup
p a.frozen? # => true
p b.frozen? # => true
p c.frozen? # => false
浅いコピーを理解した上で、参照先まで複製できる深いコピーの仕方を学びます。
深いコピー(deep copy)
Marshalモジュールを利用して複製します。
a = ["Alice","Bob","Jack"]
b = Marshal.load(Marshal.dump(a))
b[0] = "Rian"
p a # => ["Alice", "Bob", "Jack"]
p b # => ["Rian", "Bob", "Jack"]
p b[1].object_id # => 47182587105860
p a[1].object_id # => 47182587106120
配列の要素までオブジェクトIDが違うのがわかり、完全な複製ができていることがわかります。
ただし、Marshalできないオブジェクトが含まれている場合は複製できないので注意しましょう!