Rubyで配列やハッシュなどのオブジェクトのコピーを利用したいなら、=演算子ではなく、dupメソッドを使ってください。
Rubyのオブジェクトのコピーは、他のプログラム言語とは違います。dupメソッドを知らないと、知らずに不具合を作ってしまうかもしれません。今回は、dupメソッドのことをよく知らない方のために、例を使って詳しく解説します。
Rubyでオブジェクトをコピーするには
Rubyで配列やハッシュなどのオブジェクトをコピーする場合、別の変数名で参照できる方法と、同じ内容を複製する方法の2つが使えます。具体的に前者は、「=」でオブジェクトをコピーした場合で、後者はdupメソッドまたはcloneメソッドを使った場合です。
dupメソッドおよびcloneメソッドを解説する前に、Rubyにおけるオブジェクトのコピーについて説明します。
=演算子でオブジェクトをコピーした場合
C言語を学んだ方なら、変数をコピーする場合、あらかじめコピーしたデータを格納するためのメモリ領域を確保してから、コピーすることをご存じでしょう。しかし、Rubyでは、変数の宣言やメモリの確保といった処理が自動化されており、プログラマーが変数の領域を意識しなくてもプログラムを作成できます。
そして、Rubyでは、オブジェクトのコピーに=演算子を使用した場合、コピー元と全く同じものが別に作られる訳ではありません。コピー元の参照する新しいラベルが作られると考えてください。
=演算子で配列をコピーした例
a = [1, 2, 3] b = a p a # [1, 2, 3]が表示される p b # [1, 2, 3]が表示される a[0] = 10 p a # [10, 2, 3]が表示される p b # [10, 2, 3]が表示される
この例は、配列aを、=演算子でbにコピーした例です。この場合、bの内容はaと同じなので、途中でa[0]を書き換えると、b[0]も変わってしまいます。
dupメソッドでコピーした場合
先ほど、Rubyの=演算子でオブジェクトをコピーした場合の例を紹介しましたが、複製を作成する場合にはdupメソッドを使います。そして、dupメソッドを利用した場合、先ほどの例のように、コピー元のオブジェクトを書き換えると、コピー先も変わる、ということはありません。
dupメソッドで配列をコピーした例
a = [1, 2, 3] b = a.dup p a # [1, 2, 3]が表示される p b # [1, 2, 3]が表示される a[0] = 10 p a # [10. 2. 3]が表示される p b # [1, 2, 3]が表示される
dupメソッドで複製を作った場合、先ほどの=演算子を使った例のように、配列aのa[0]を書き換えても、b[0]が変わることはありません。
なお、Rubyには、dupメソッドとほぼ同じ機能のcloneメソッドもあり、同じように使うことが可能です。
文字列や変数はどうなる
Rubyのプログラムで扱う変数やメソッドなどは全てオブジェクトの一部です。しかし、先ほどのように、全てのオブジェクトで=演算子を使うと、新しいラベルが追加される、という訳ではありません。
例えば、数字を格納する変数をコピーすると、その複製が作成されます。コピー先を修正しても、元の変数の数字が書き換わることはありません。また、文字列も同じです。
dupメソッドやcloneメソッドが使えるオブジェクトと不要なオブジェクトがあることを理解して、うまく=演算子と使い分けられるようになりましょう。
dupメソッドの基本
先ほど説明したように、Rubyではオブジェクトの複製を作成するdupメソッドが使えます。次から、dupメソッドの文法と基本的な使い方を解説します。
dupメソッドの文法
dupメソッドは、オブジェクトの複製を返すメソッドです。また、メソッド単体で使用し、引数やブロック構文はありません。
オブジェクト.dup
dupメソッドを使った簡単な例
a = [1, 2, 3] b = a.dup p a # [1, 2. 3]が表示される p b # [1, 2, 3]が表示される
cloneメソッドの基礎
先ほど説明したdupメソッドと同じ機能のメソッドとして、cloneメソッドも利用できます。ただし、cloneメソッドは、dupメソッドと同じではありません。次に、cloneメソッドの基本について説明します。
cloneメソッドの文法
cloneメソッドは、先ほど説明したdupメソッドと同じくオブジェクトの複製を返すメソッドで、freezeを含めて複製できます。
なお、freezeとは、オブジェクトの内容を禁止するfreezeメソッドを適用した状態で、この状態のオブジェクトは変更できません。cloneは、freeze状態もいっしょにコピーします。
オブジェクト.clone( freezeの有無 )
freezeの有無を省略した場合は、freeze状態もいっしょにコピーしますが、falseを指定した場合は、freezeをコピーせず、dupを全く同じ機能です。
cloneメソッドの簡単な使用例
a = [1, 2, 3] a.freeze b = a.clone p a # [1, 2. 3]が表示される p b # [1, 2, 3]が表示される
この場合、配列aはfreezeメソッドが適用されているので、修正できません。そして、cloneメソッドでコピーされた配列bも、freeze状態がいっしょにコピーされているので、修正できなくなっています。
freezeの解除はdupで
オブジェクトをfreeze状態にしてしまうと、そのオブジェクトは修正できません。そして、先ほど説明したように、cloneメソッドでコピーを作成すると、freeze状態もいっしょにコピーされるので、やはり修正できません。
しかし、dupは、freeze状態をコピーしません。freeze状態のオブジェクトをコピーし、その一部を変更した配列を利用する場合は、dupメソッドを活用してください。
なお、オブジェクトがfreeze状態かどうかは、frozen?メソッドでチェックできます。
Railsではdeep_dupが使える
これまで紹介したきたdupメソッドとclonseメソッドを使用して複製を作っても、オブジェクトの参照先まではコピーされません。もし、そこまでのコピーが必要なら、Ruby on Railsのdepp_dupの利用を検討してください。
dupとcloneは浅いコピー
dupメソッドとcloneメソッドでオブジェクトの複製を作成しても、オブジェクトの指している先までは複製されません。これを浅いコピーと言い、逆にオブジェクトの指している先までコピーするケースは、深いコピーと呼びます。
Rubyの標準機能で深いコピーを実現するには、Marshalモジュールで可能ですが、使い易いとは言えません。そのため、dupメソッドと同じようにつかえる、Ruby on Railsのdeep_dupの利用を検討してください。
deep_dupの基本
先ほど説明したように、deep_dupメソッドは、オブジェクトの深いコピーを返します。
オブジェクト.deep_dup
deep_dupを使った例
require 'active_support/all' hash1 = { a: 'test', b: 2, c: 3 } hash2 = hash1.deep_dup
今回紹介するdeep_dupメソッドは、Ruby on Railsの機能の一部ですが、active_supportをインストールすることで、Ruby on Rails以外の環境でも利用できます。上の例では、require ‘active_support/all’で、Ruby on Railsの機能を利用し、deep_dupメソッドを使っています。
まとめ
これまで説明したように、Rubyでオブジェクトのコピーを利用する場合、=演算子でコピーしただけでは、十分ではありません。配列やハッシュなどのオブジェクトの複製を作る場合は、dupメソッドまたはcloneメソッドを利用してください。
ただし、dupメソッドやcloneメソッドを使用しても、完全なコピーは作れません。もし、Rubyのプログラムの中で、オブジェクトの完全なコピーを使用する場合は、Ruby on Railsの機能の一部であるdeep_dupを利用する方法もあります。
ぜひ、dupメソッドとcloneメソッドの使い方を理解して、Rubyのプログラムに活かしてください。