Rubyのdupメソッドとcloneメソッドの違いは何ですか?


214

言うためdupRubyドキュメント

一般的に、cloneそしてdup子孫クラスで異なる意味を有することができます。ながらcloneその内部状態を含むオブジェクトを複製するために使用され、dup典型的には、新しいインスタンスを作成するために、子孫オブジェクトのクラスを使用します。

しかし、いくつかのテストを行ったところ、実際には同じであることがわかりました。

class Test
   attr_accessor :x
end

x = Test.new
x.x = 7
y = x.dup
z = x.clone
y.x => 7
z.x => 7

では、2つの方法の違いは何ですか?


29
私は、単に違いを知らなかった望む何を dupしてcloneいますが、なぜあなたは他のではなく、1を使用すると思います。
アンドリューグリム

1
ここにも良いリンクがあります-coderwall.com/p/1zflyg
Arup Rakshit 2013

回答:


298

サブクラスは、これらのメソッドをオーバーライドして、異なるセマンティクスを提供できます。それObject自体、2つの重要な違いがあります。

まず、cloneシングルトンクラスをコピーしますが、コピーしdupません。

o = Object.new
def o.foo
  42
end

o.dup.foo   # raises NoMethodError
o.clone.foo # returns 42

次に、clone凍結状態を保持しますが、保持しdupません。

class Foo
  attr_accessor :bar
end
o = Foo.new
o.freeze

o.dup.bar = 10   # succeeds
o.clone.bar = 10 # raises RuntimeError

多くの場合、これらのメソッドRubinius実装は、これらの質問に対する回答の私のソースです。これは非常に明確であり、かなり準拠したRuby実装だからです。


15
Rubyで明確に定義された用語である「シングルトンクラス」には、シングルトンメソッドだけでなく、シングルトンクラスで定義されている定数も含まれます。検討してくださいo = Object.new; class << o; A=5; end; puts ( class << o.clone; A; end ); puts ( class << o.dup; A; end )
Jeremy Roman

3
すばらしい答えに続いてすばらしいコメントがありましたが、その構文を理解するために野生のガチョウを追いかけました。これは、混乱しそうな他の人を助けるでしょう:devalot.com/articles/2008/09/ruby-singleton
davidpm4

1
「シングルトンクラス」にはextend、元のオブジェクトで編集されたすべてのモジュールも含まれることに言及する価値があると思います。したがって、Object.new.extend(Enumerable).dup.is_a?(Enumerable)falseを返します。
ダニエル

この答えは質問に答え、違いを述べていますが。また、Object#dupのドキュメントで述べられているように、両方のメソッドが異なる状況で使用されることも注目に値します。cloneの使用例は、オブジェクトを同じインスタンスとして使用することを目的として(異なるオブジェクトIDを使用しながら)オブジェクトを複製することですが、dupは新しいインスタンスのベースとしてオブジェクトを複製することを目的としています。
3limin4t0r 2018年

189

ActiveRecordを扱う場合にも大きな違いがあります。

dup IDを設定せずに新しいオブジェクトを作成するため、次のコマンドを実行して、新しいオブジェクトをデータベースに保存できます。 .save

category2 = category.dup
#=> #<Category id: nil, name: "Favorites"> 

clone 同じIDで新しいオブジェクトを作成するので、その新しいオブジェクトに加えられたすべての変更は、ヒットした場合、元のレコードを上書きします .save

category2 = category.clone
#=> #<Category id: 1, name: "Favorites">

43
この答えは、IMOが最も重要な実用的な情報を持っているものです...他の答えは、中等教育に宿りますが、この答えは、重要な実用的な違いを正確に示しています。
jpw 2015年

37
上記はActiveRecordに固有のものです。標準のRubyでは、その区別ははるかに微妙です。
ahmacleod 2015

1
@Stefanと@jvalanen:オブジェクトにメソッドdupcloneメソッドを適用するとActiveRecord、回答で述べた内容とは逆の結果が得られます。つまり、を使用すると、設定されたdup状態で新しいオブジェクトが作成idされ、使用中cloneにはid設定されていない状態でオブジェクトが作成されます。もう一度調べて、クリアしてください。。Thnx
huzefa biyawarwala

Rails 5でも何も変更されていません:api.rubyonrails.org/classes/ActiveRecord/…。だから私はあなたの場合に特別な何かがあると思います...
jvalanen

しかし、clone保存されたことのない新しいレコードを使用することは、それでかなり安全でしょうか?この方法で「テンプレートオブジェクト」を作成し、それを複製して特定のインスタンスを保存できますか?
Cyril Duchon-Doris

30

1つの違いは、凍結オブジェクトです。clone(一方、凍結対象のも凍結されたdup凍結オブジェクトのではありません)。

class Test
  attr_accessor :x
end
x = Test.new
x.x = 7
x.freeze
y = x.dup
z = x.clone
y.x = 5 => 5
z.x = 5 => TypeError: can't modify frozen object

もう1つの違いは、シングルトンメソッドです。同じ話ですが、dupそれらはコピーされませんが、コピーされますclone

def x.cool_method
  puts "Goodbye Space!"
end
y = x.dup
z = x.clone
y.cool_method => NoMethodError: undefined method `cool_method'
z.cool_method => Goodbye Space!

これは私にとって非常に役に立ちました。凍結された定数値を作成し、それを次のようなものに渡した場合:github.com/rack/rack/blob/master/lib/rack/utils.rb#L248(Rails cookie processing)簡単にエラーが発生する可能性があります彼らがあなたに知らないとき、彼らはそれを複製し、それからその複製を修正しようとします。凍結された値を複製して渡すことで、ここでラックを壊すことなく、誰かが誤って定数を変更しないことを少なくとも保証できます。
XP84

4

どちらもほぼ同じですが、クローンはdupよりも1つ多くのことを行います。クローンでは、オブジェクトのフリーズ状態もコピーされます。dupでは、常に解凍されます。

 f = 'Frozen'.freeze
  => "Frozen"
 f.frozen?
  => true 
 f.clone.frozen?
  => true
 f.dup.frozen?
  => false 

4

新しいドキュメントは、良い例が含まれています。

class Klass
  attr_accessor :str
end

module Foo
  def foo; 'foo'; end
end

s1 = Klass.new #=> #<Klass:0x401b3a38>
s1.extend(Foo) #=> #<Klass:0x401b3a38>
s1.foo #=> "foo"

s2 = s1.clone #=> #<Klass:0x401b3a38>
s2.foo #=> "foo"

s3 = s1.dup #=> #<Klass:0x401b3a38>
s3.foo #=> NoMethodError: undefined method `foo' for #<Klass:0x401b3a38>

0

cloneを使用して、Rubyでプロトタイプベースのプログラミングを行うことができます。RubyのObjectクラスは、cloneメソッドとdupメソッドの両方を定義します。cloneとdupはどちらも、コピーするオブジェクトの浅いコピーを作成します。つまり、オブジェクトのインスタンス変数はコピーされますが、参照するオブジェクトはコピーされません。例を示します。

class Apple
  attr_accessor :color
  def initialize
    @color = 'red'
  end
end

apple = Apple.new
apple.color
 => "red"
orange = apple.clone
orange.color 
 => "red"
orange.color << ' orange'
 => "red orange" 
apple.color
 => "red orange"

上記の例では、オレンジ色のクローンがappleオブジェクトの状態(つまり、インスタンス変数)をコピーしていますが、appleオブジェクトが他のオブジェクト(Stringオブジェクトの色など)を参照している場合、それらの参照はコピーされません。代わりに、リンゴとオレンジはどちらも同じオブジェクトを参照します。この例では、参照は文字列オブジェクト「red」です。orangeが追加メソッド<<を使用して既存のStringオブジェクトを変更すると、文字列オブジェクトが「red orange」に変更されます。どちらも同じStringオブジェクトを指しているため、これは実際にはapple.colorも変更します。

補足として、代入演算子=は新しいオブジェクトを代入するため、参照を破棄します。ここにデモがあります:

class Apple
  attr_accessor :color
  def initialize
    @color = 'red'
  end
end

apple = Apple.new
apple.color
=> "red"
orange = apple.clone
orange.color
=> "red"
orange.color = 'orange'
orange.color
=> 'orange'
apple.color
=> 'red'

上記の例では、オレンジのクローンのカラーインスタンスメソッドに新しい新しいオブジェクトを割り当てたときに、アップルと同じオブジェクトを参照しなくなりました。したがって、今度はappleのcolorメソッドに影響を与えずにorangeのcolorメソッドを変更できますが、appleから別のオブジェクトを複製すると、その新しいオブジェクトはコピーされたインスタンス変数内の同じオブジェクトをappleとして参照します。

dupは、コピーするオブジェクトの浅いコピーも作成します。dupに対して上記と同じデモを行うと、まったく同じように機能することがわかります。しかし、cloneとdupの間には2つの大きな違いがあります。最初に、他の人が述べたように、クローンはフリーズ状態をコピーし、dupはコピーしません。これは何を意味するのでしょうか?Rubyの「凍結」という用語は不変の難解な用語であり、それ自体がコンピュータサイエンスの命名法であり、変更できないものを意味します。したがって、Rubyでフリーズされたオブジェクトを変更することはできません。実際には不変です。フリーズしたオブジェクトを変更しようとすると、RubyはRuntimeError例外を発生させます。cloneは凍結状態をコピーするため、複製されたオブジェクトを変更しようとすると、RuntimeError例外が発生します。逆に、dupは凍結状態をコピーしないため、

class Apple
  attr_accessor :color
  def initialize
    @color = 'red'
  end
end

apple = Apple.new
apple.frozen?
 => false 
apple.freeze
apple.frozen?
 => true 
apple.color = 'crimson'
RuntimeError: can't modify frozen Apple
apple.color << ' crimson' 
 => "red crimson" # we cannot modify the state of the object, but we can certainly modify objects it is referencing!
orange = apple.dup
orange.frozen?
 => false 
orange2 = apple.clone
orange2.frozen?
 => true 
orange.color = 'orange'
 => "orange" # we can modify the orange object since we used dup, which did not copy the frozen state
orange2.color = 'orange'
RuntimeError: can't modify frozen Apple # orange2 raises an exception since the frozen state was copied via clone

次に、さらに興味深いことに、クローンはシングルトンクラス(およびそのメソッド)をコピーします。これは、Rubyでプロトタイプベースのプログラミングを行う場合に非常に役立ちます。最初に、シングルトンメソッドが実際に複製でコピーされ、次にRubyでのプロトタイプベースのプログラミングの例にそれを適用できることを示しましょう。

class Fruit
  attr_accessor :origin
  def initialize
    @origin = :plant
  end
end

fruit = Fruit.new
 => #<Fruit:0x007fc9e2a49260 @origin=:plant> 
def fruit.seeded?
  true
end
2.4.1 :013 > fruit.singleton_methods
 => [:seeded?] 
apple = fruit.clone
 => #<Fruit:0x007fc9e2a19a10 @origin=:plant> 
apple.seeded?
 => true 

ご覧のように、フルーツオブジェクトインスタンスのシングルトンクラスがクローンにコピーされます。したがって、クローンオブジェクトはシングルトンメソッド:seeded?にアクセスできます。しかし、これはdupには当てはまりません。

apple = fruit.dup
 => #<Fruit:0x007fdafe0c6558 @origin=:plant> 
apple.seeded?
=> NoMethodError: undefined method `seeded?'

現在、プロトタイプベースのプログラミングでは、他のクラスを拡張し、ブループリントとして機能する親クラスからメソッドが派生するクラスのインスタンスを作成するクラスはありません。代わりに、ベースオブジェクトがあり、そのメソッドと状態がコピーされたオブジェクトから新しいオブジェクトを作成します(もちろん、クローンを介して浅いコピーを行っているため、インスタンス変数が参照するオブジェクトはJavaScriptと同じように共有されますプロトタイプ)。次に、複製されたメソッドの詳細を入力することにより、オブジェクトの状態を入力または変更できます。以下の例では、ベースフルーツオブジェクトがあります。すべての果物には種子があるので、number_of_seedsメソッドを作成します。ただし、リンゴにはシードが1つあるため、クローンを作成して詳細を入力します。これで、appleのクローンを作成するときに、メソッドのクローンを作成するだけでなく、状態のクローンも作成しました!cloneは状態(インスタンス変数)の浅いコピーを行うことを覚えておいてください。そのため、red_appleを取得するためにappleのクローンを作成すると、red_appleには自動的に1つのシードがあります。red_appleは、Appleから継承するオブジェクトであり、AppleはFruitから継承するオブジェクトと考えることができます。したがって、それが私がフルーツとアップルを活用した理由です。クラスとオブジェクトの違いはクローンのおかげでなくなりました。

Fruit = Object.new
def Fruit.number_of_seeds=(number_of_seeds)
  @number_of_seeds = number_of_seeds
end
def Fruit.number_of_seeds
  @number_of_seeds
end
 Apple = Fruit.clone
 => #<Object:0x007fb1d78165d8> 
Apple.number_of_seeds = 1
Apple.number_of_seeds
=> 1
red_apple = Apple.clone
 => #<Object:0x007fb1d892ac20 @number_of_seeds=1> 
red_apple.number_of_seeds
 => 1 

もちろん、プロトタイプベースのプログラミングでコンストラクタメソッドを使用できます。

Fruit = Object.new
def Fruit.number_of_seeds=(number_of_seeds)
  @number_of_seeds = number_of_seeds
end
def Fruit.number_of_seeds
  @number_of_seeds
end
def Fruit.init(number_of_seeds)
  fruit_clone = clone
  fruit_clone.number_of_seeds = number_of_seeds
  fruit_clone
end
Apple = Fruit.init(1)
 => #<Object:0x007fcd2a137f78 @number_of_seeds=1> 
red_apple = Apple.clone
 => #<Object:0x007fcd2a1271c8 @number_of_seeds=1> 
red_apple.number_of_seeds
 => 1 

最終的には、クローンを使用して、JavaScriptプロトタイプの動作に似たものを取得できます。

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.