0
0

More than 1 year has passed since last update.

【Ruby】オブジェクトをコピーするときの注意

Posted at

書籍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できないオブジェクトが含まれている場合は複製できないので注意しましょう!

参考

0
0
2

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0