Ruby
 Computer >> コンピューター >  >> プログラミング >> Ruby

Rubyのdupとcloneの違いを徹底解説!オブジェクト複製の基礎知識

Rubyではオブジェクトをコピーできることをご存じでしょうか?しかも、コピー用のメソッドは2種類用意されています。

そのメソッドとは

  • dup
  • clone

それぞれの違いについては後ほど詳しく見ていきますが、まず最初に…

そもそもなぜオブジェクトをクローンする必要があるのでしょうか?

Rubyの多くのオブジェクトはミュータブル(変更可能)です。つまり、作成した後に内容を書き換えることができます。

元のオブジェクトはそのまま保持しつつ、内容を変更したい場合には、クローン(複製)を作成するのが有効です。

具体例を挙げてみましょう。

「先頭以外のすべての要素を含む配列」が欲しい場面を想定します。

一つ目の方法は、範囲指定で新しい配列を取り出すことです。

a = [1,2,3,4,5]

a[1..-1]
# [2,3,4,5]

二つ目の方法は、cloneしてから要素を削除することです。

b = a.clone

b.shift
# 1

b
# [2,3,4,5]

どちらの方法でも、元の配列aはそのまま保持されます。

Rubyのdupとcloneの違いを徹底解説!オブジェクト複製の基礎知識

フリーズされたオブジェクトの扱い

dupとcloneは、mapcollectのように単なるエイリアス(別名)の関係ではありません。両者にはいくつかの重要な違いがあります。

2つのものの共通点と相違点を比較することは、理解を深めるための非常に良い方法です。

どちらのメソッドもオブジェクトをコピーする点は同じですが、dupはオブジェクトの内部状態をコピーしないという点が異なります。

具体的には、以下の3つの状態が該当します。

  • frozen状態(フリーズ状態)
  • tainted状態(汚染状態)
  • 特異クラス(singleton class)

実際に確認してみましょう

a = Object.new.freeze

b = a.dup
b.frozen?
# false

b = a.clone
b.frozen?
# true

このように、freezeされたオブジェクトをdupしても、複製先はフリーズされません。一方、cloneを使うとfrozen状態もそのまま引き継がれます。

さらに、Ruby 2.4以降では、cloneメソッドにオプションを渡すことで、フリーズ状態を制御できるようになりました。

使用例

a.clone(freeze: true)
a.clone(freeze: false)

freeze: trueを指定すれば常にフリーズされた複製が作られ、freeze: falseを指定すれば元のオブジェクトがフリーズされていても、フリーズされていない複製を作成できます。

浅いコピー(Shallow Copy)と深いコピー(Deep Copy)

オブジェクトのコピーに関しては、もう一つ知っておくべき重要なポイントがあります。

dupまたはcloneのどちらでコピーしても、作られるのは常に浅いコピー(shallow copy)です。

これは、オブジェクトの中に含まれる別のオブジェクト(ネストされた要素)まではコピーされないという意味です。

言い換えると

文字列の配列をコピーした場合、複製されるのはあくまで配列自体だけで、中に入っている文字列そのものまでは複製されません。

object_idを使って自分の目で確かめてみましょう:

original = %w(apple orange banana)
copy     = original.clone

original.map(&:object_id)
# [23506500, 23506488, 23506476]

copy.map(&:object_id)
# [23506500, 23506488, 23506476]

配列をクローンした後も各要素のobject_idは同じままです。つまり、コピー先の配列は元と同じ文字列オブジェクトを共有していることが分かります。

この問題は、次のようにして解決できます:

strings.clone.map(&:clone)

このコードなら、配列と文字列の両方が複製されます。ただし注意が必要なのは、この方法でもコピーされるのは1階層のみだという点です。より深い階層まで完全に複製したい場合は、ActiveSupplyではなくActiveSupportが提供するdeep_dupメソッドを利用するとよいでしょう。

まとめ

今回はRubyにおけるオブジェクトの複製について学びました。dupcloneの違い、そして浅いコピーと深いコピーの違いまでを解説しました。

これらの挙動の違いを正しく理解しておくことで、意図しない副作用によるバグを防ぎ、より安全なコードを書けるようになります。ぜひ日々の開発で活用してみてください!

最後までお読みいただきありがとうございました!

  1. Rubyのputs・print・pの違いとは?出力メソッドの使い分けを徹底解説

    Rubyには、画面に何かを出力するための方法がいくつかあります。中でも特によく使われるのが、以下の3つのメソッドです。 puts print p しかし、これらは一体どう違うのでしょうか?そしてどんな場面でどれを使えばよいのか?この記事では、それぞれの特徴と使い分けのポイントを詳しく解説します。 改行なしで出力する方法 ユーザーに見せるために画面へ何かを出力したいときは、通常putsを使用します。 例: puts Hello there! putsは、実行するたびにメッセージの末尾へ自動的に改行を追加します。 もし改行したくない場合は、printを使いましょう。 例: print 1

  2. 【Ruby】デコレータデザインパターンでクラスを変更せずにオブジェクトを拡張する方法

    デコレータ(Decorator)デザインパターンとは、どのようなものなのでしょうか? また、このパターンをRubyのプロジェクトではどのように活用すればよいのでしょうか? デコレータデザインパターンを利用すると、クラスそのものを変更することなく、オブジェクトに新しい機能を追加して拡張することができます。 それでは、具体的な例を見ていきましょう! ログ出力とパフォーマンス計測の実装例 この例では、rest-clientのようなGemを使ってHTTPリクエストを送信します。 コードは次のようになります。 require restclient data = RestClient.get(www.