Rubyでのオブジェクトコピー dupとcloneについて

Rubyでのオブジェクトコピー dupとcloneについて

みなさんこんにちは

意識高い系エンジニアの僕です。

最近Rubyでのオブジェクトコピーについていくつか質問されたので、まずは初歩的なところをまとめてみました。

dupとclone

Rubyでオブジェクトをコピーする場合、Object#dupObject#cloneが使えますが、
これらは以下のように挙動が違います。

メソッド名 凍結 汚染 信頼 特異
dup
clone

cloneメソッドはオブジェクトの凍結状態、汚染状態、信頼状態をコピーする。
cloneメソッドはオブジェクトの特異メソッドをコピーする。
dupメソッドはオブジェクトの汚染状態、信頼状態をコピーする。
dupメソッドはオブジェクトの特異メソッドをコピーしない。

シャローコピーとディープコピー

シャローコピー

clonedupも、ともにシャローコピー(浅いコピーのため)、オブジェクトの参照先
まではコピーしません。

class Dog
  attr_accessor :name, :age

  def initialize(name: nil, age: nil)
    self.name = name
    self.age = age
  end
end

dog1 = Dog.new(name: 'pochi', age: 10)
dog2 = dog1.dup
dog2.name.gsub!(/po/, 'ha')

p dog1.name
p dog2.name
ruby duptest.rb
"hachi"
"hachi"

この例ではdog1からcloneしたdog2のnameプロパティの値を変更すると、dog1のnameプロパティの値も変更されています。
これは指し示しているオブジェクトが同じだからです。

class Dog
  attr_accessor :name, :age

  def initialize(name: nil, age: nil)
    self.name = name
    self.age = age
  end
end

dog1 = Dog.new(name: 'pochi', age: 10)
dog2 = dog1.dup

p dog1.name.object_id
p dog2.name.object_id
ruby duptest.rb
70213018418320
70213018418320

nameプロパティに格納されている文字列オブジェクトのobject_idが同じなのがわかります。

ディープコピー

オブジェクトの参照先までコピーする場合、RubyではMarshal.dumpMarshal.load
を使うことで実現できます。

class Dog
  attr_accessor :name, :age

  def initialize(name: nil, age: nil)
    self.name = name
    self.age = age
  end
end

dog1 = Dog.new(name: 'pochi', age: 10)
dog2 = Marshal.load(Marshal.dump(dog1))
dog2.name.gsub!(/po/, 'ha')

p dog1.name
p dog2.name
ruby duptest.rb
"pochi"
"hachi"

この例ではdog1からMarshal.dumpMarshal.loadでコピーしたdog2の
nameプロパティの値を変更しても、dog1のnameは変更されません。
これは、Marshal.dumpMarshal.loadでコピーした場合は、name
指し示している文字列オブジェクトが新しく生成されて格納されたためです。

class Dog
  attr_accessor :name, :age

  def initialize(name: nil, age: nil)
    self.name = name
    self.age = age
  end
end

dog1 = Dog.new(name: 'pochi', age: 10)
dog2 = Marshal.load(Marshal.dump(dog1))
dog2.name.gsub!(/po/, 'ha')

p dog1.name.object_id
p dog2.name.object_id
ruby duptest.rb
70297823591600
70297823591380

nameプロパティに格納されている文字列オブジェクトのobject_idが違うのがわかります。

Marshalモジュールとは

Marshalモジュールは、Rubyオブジェクトをファイル(または文字列)に書き出したり、
読み戻したりする機能を提供するモジュールです。
Marshal.dumpはオブジェクトをファイルに書き出すメソッドで、Marshal.dump
書き出したファイルを読み込んでオブジェクトを生成するメソッドです。
Rubyでディープコピーをする方法として、この2つのメソッドを使う方法がよく紹介されていますね。

Marshalモジュールを使う場合の注意点

Marshalでファイルに書き出せない以下の様なオブジェクトをMarshalしようとすると、TypeErrorが発生します。

  • 名前のついてない Class/Module オブジェクト
  • システムがオブジェクトの状態を保持するもの。具体的には以下のイン スタンス。Dir, File::Stat, IO とそのサブクラス File, Socket など
  • MatchData, Data, Method, UnboundMethod, Proc, Thread, ThreadGroup, Continuation のインスタンス。
  • 特異メソッドを定義したオブジェクト

次回はRailsでのオブジェクトコピーについてまとめてみようと思います。

TAG

  • このエントリーをはてなブックマークに追加
金子 将範
エンジニア 金子 将範 rubyist

新しいことや難しい課題に挑戦することにやりがいを感じ、安定やぬるい事は退屈だと感じます。 考えるより先に手が動く、肉体派エンジニアで座右の銘は諸行無常。 大事なのは感性、プログラミングにおいても感覚で理解し、感覚で書きます。