こんにちは!エンジニアのタナカゴダイです。
rubyのオブジェクトをコピーして、コピー先のオブジェクトの中身を変更したとき、「コピー元のオブジェクトの中身も変わってしまった…」というような経験はありませんか?
そのような問題を解決する方法として、オブジェクトのコピーを作成するdupメソッドがあります。
【基礎】dupメソッドとは何か?
【基礎】dupメソッドの使い方
【発展】dupメソッドの類似メソッドであるcloneメソッドとは?
【発展】dupメソッドとcloneメソッドの違い
などのdupメソッドに関連する基本から応用的な内容についても解説していきます。
dupメソッドとは
dupメソッドとは、オブジェクトのコピーを作成するメソッドです。
dupメソッドはオブジェクトのインスタンス変数をコピーした後に、Objectクラスのinitialize_copyメソッドを呼び出します。
そして、Objectクラスが持つインスタンスメソッドなので、オブジェクトに続けてメソッドを使用することができます。
dupメソッドの基本的な書き方を以下に記載します。
original_object = ["a", "b", "c"] copied_object = original_object.dup p copied_object
["a", "b", "c"]
上記サンプルプログラムでは、original_objectの内容をdupメソッドでcopied_objectにコピーを作成しています。
出力結果を確認すると、original_objectの値がcopied_objectにコピーされていることがわかります。
dupメソッドの基本的な使い方
ここでは、dupメソッドは「どのようにオブジェクトをコピーしているか」を、サンプルプログラムを用いて詳しく解説していきます。
初めに結論から述べると、dupメソッドはオブジェクトの参照値をコピーして新しくオブジェクトを作成しています。
つまり、dupメソッドを使用してオブジェクトをコピーすると、オブジェクトが持つidが新しく生成されるのです。
この挙動はdupメソッドを使用した場合と、=を使用した場合のobject_idの変化を確認しながら理解するといいでしょう。
以下のサンプルプログラムでは、aに配列[{:a=>1}, {:b=>2}, {:c=>3}]を代入し、そのaをbに代入、さらにbをdupメソッドでコピーしています。
この時のa, b, cそれぞれのobject_idを①で確認します。
さらに、b[0]の値を変更し、a, b, cそれぞれの値の確認を②で行っています。
a = [{:a=>1}, {:b=>2}, {:c=>3}] b = a c = b.dup # ①:object_idの確認 p a.object_id # aのobject_id p b.object_id # bのobject_id p c.object_id # cのobject_id b[0] = {:a=>111} # ②:値の確認 p a # aの値 p b # bの値 p c # cの値
上記サンプルプログラムの出力結果は以下の通りです。
# ①:object_idの確認 6690120 # aのobject_id 6690120 # bのobject_id 6628380 # cのobject_id # ②:値の確認 [{:a=>111}, {:b=>2}, {:c=>3}] # aの値 [{:a=>111}, {:b=>2}, {:c=>3}] # bの値 [{:a=>1}, {:b=>2}, {:c=>3}] # cの値
①の結果では、aのobject_idとbのobject_idが等しくなっています。
=を使用した場合は、bにaというオブジェクトを代入する、という挙動になるためaとbのobject_idが等しくなります。
対して、cのobject_idはdupメソッドを使用したことにより、新しいobject_idになっていることがわかります。
そして、object_idが異なるということは②の結果に影響を与えます。
②では、b[0]の値を変更して、a, b, cそれぞれの値を出力していますが、①で分かった通りaとbは同じオブジェクトを参照しているため、b[0]の値を変更するとaの出力結果も変わってきます。
cは、a, bと異なるオブジェクトを参照しているため、b[0]の値を変更してもcの値の出力結果には影響が出ないというわけです。
このようにして、dupメソッドは値をコピーして新たにオブジェクトを作成するメソッドなのです。
dupメソッドとcloneメソッドの違いとは
dupメソッドに類似しているメソッドとして、cloneメソッドがあります。
cloneメソッドとは、dupメソッドと同様にオブジェクトをコピーして作成するメソッドです。
書き方はdupメソッドと変わりなく、オブジェクトに続けてcloneメソッドを使用します。
origin = ["a", "b", "c"] clone = origin.clone p clone
["a", "b", "c"]
では、dupメソッドとcloneメソッドでは何が違うのでしょうか。
どちらもオブジェクトをコピーするメソッドですが、以下の点において、2つのメソッドに差異が生じます。
・cloneメソッドは凍結状態/汚染状態/信頼状態/特異メソッドまでコピー
・dupメソッドは汚染状態/信頼状態をコピーし、凍結状態/特異メソッドはコピーしない
以下の表で、dupメソッドとcloneメソッドの差異についてまとめました。
メソッド | 凍結状態 | 汚染状態 | 信頼状態 | 特異メソッド |
---|---|---|---|---|
dupメソッド | コピーしない | コピーする | コピーする | コピーしない |
cloneメソッド | コピーする | コピーする | コピーする | コピーする |
それでは実際にdupメソッドとcloneメソッドの違いを確認してみましょう。
凍結状態
凍結状態とは、オブジェクトが変更可能かどうかを示す状態のことです。
Objectクラスがもつfreezeメソッドで凍結状態に変更し、frozen?メソッドで凍結状態かどうかを確認することができます。
dupメソッドとcloneメソッドを使用した際のオブジェクトの凍結状態について、実際のサンプルプログラムで確認しましょう。
a = "aaa" a.freeze # 凍結状態にする p a.frozen? # 凍結状態を確認(1-1) a_dup = a.dup p a_dup.frozen? # dupの凍結状態を確認(1-2) a_clone = a.clone p a_clone.frozen? # cloneの凍結状態を確認(1-3)
true false true
出力結果より、dupメソッドは凍結状態をコピーせず、cloneメソッドは凍結状態をコピーすることがわかります。
汚染状態
汚染状態とは、オブジェクトが汚染されているかどうかを示す状態のことです。
Objectクラスがもつtaintメソッドで汚染状態に変更し、untaintメソッドで汚染状態を解除できます。
また、tainted?メソッドで汚染状態かどうかを確認することができます。
dupメソッドとcloneメソッドを使用した際のオブジェクトの汚染状態について、実際のサンプルプログラムで確認しましょう。
a = "aaa" a.taint # 汚染状態にする p a.tainted? # 汚染状態を確認(2-1) a_dup = a.dup p a_dup.tainted? # dupの汚染状態を確認(2-2) a_clone = a.clone p a_clone.tainted? # cloneの汚染状態を確認(2-3)
true true true
出力結果より、dupメソッドとcloneメソッドはどちらも汚染状態をコピーすることがわかります。
信頼状態
信頼状態とは、オブジェクトが信頼されているかどうかを示す状態のことです。
Objectクラスがもつtrustメソッドで信頼状態に変更し、untrustメソッドで信頼されていない状態に変更します。
また、untrusted?メソッドで信頼状態かどうかを確認することができます。
dupメソッドとcloneメソッドを使用した際のオブジェクトの信頼状態について、実際のサンプルプログラムで確認しましょう。
a = "aaa" a.untrust # 信頼されない状態にする p a.untrusted? # 信頼状態を確認(3-1) a_dup = a.dup p a_dup.untrusted? # dupの信頼状態を確認(3-2) a_clone = a.clone p a_clone.untrusted? # cloneの信頼状態を確認(3-3)
true true true
出力結果より、dupメソッドとcloneメソッドはどちらも信頼状態をコピーすることがわかります。
特異メソッド
最後に特異メソッドについて確認します。
特異メソッドとは、簡潔に説明すると対象のオブジェクトが固有で持つメソッドのことです。
以下は、aというオブジェクトにsingular_methodという特異メソッドを定義し、この特異メソッドsingular_methodが、dupメソッドとcloneメソッドでコピーされるか否かを確認するサンプルプログラムです。
a = "aaa" # aに特異メソッドsingular_methodを定義 def a.singular_method p "特異メソッド" end a.methods.grep(/singular_method/) # aがsingular_methodを持っているか調べる(4-1) a.singular_method # singular_methodを実行(4-2) a_dup = a.dup a_dup.methods.grep(/singular_method/) # (4-3) a_dup.singular_method # (4-4) a_clone = a.clone a_clone.methods.grep(/singular_method/) # (4-5) a_clone.singular_method # (4-6)
[:singular_method] # (4-1) "特異メソッド" # (4-2) [] # (4-3) NoMethodError: undefined method `singular_method' for "aaa":String # (4-4) [:singular_method] # (4-5) "特異メソッド" # (4-6)
出力結果より、dupメソッドはNoMethodErrorが出ているため特異メソッドsingular_methodまではコピーしていないことがわかり、cloneメソッドは特異メソッドsingular_methodまでコピーしていることがわかります。
まとめ
この記事では、dupメソッドの使い方をサンプルプログラムを用いて解説しました。
dupメソッドは、オブジェクトのコピーを作成するメソッドです。
また、dupメソッド以外のオブジェクトのコピーを作成するcloneメソッドについても違いとともに解説しました。
ぜひこの機会にdupメソッド/cloneメソッドをマスターしてください!