Ruby 的 dup 和 clone 方法有什么区别?

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/10183370/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-09-06 05:01:31  来源:igfitidea点击:

What's the difference between Ruby's dup and clone methods?

rubyclonedup

提问by cali-1337500

The Ruby docs for dupsay:

Ruby的文档的dup说:

In general, cloneand dupmay have different semantics in descendent classes. While cloneis used to duplicate an object, including its internal state, duptypically uses the class of the descendent object to create the new instance.

在一般情况下,clonedup可能在派生类不同的语义。Whileclone用于复制对象,包括其内部状态,dup通常使用后代对象的类来创建新实例。

But when I do some test I found they are actually the same:

但是当我做一些测试时,我发现它们实际上是一样的:

class Test
   attr_accessor :x
end

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

So what are the differences between the two methods?

那么这两种方法有什么区别呢?

回答by Jeremy Roman

Subclasses may override these methods to provide different semantics. In Objectitself, there are two key differences.

子类可以覆盖这些方法以提供不同的语义。就Object其本身而言,有两个关键区别。

First, clonecopies the singleton class, while dupdoes not.

首先,clone复制单例类,而dup不会。

o = Object.new
def o.foo
  42
end

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

Second, clonepreserves the frozen state, while dupdoes not.

其次,clone保留冻结状态,而dup没有。

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

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

The Rubinius implementation for these methodsis often my source for answers to these questions, since it is quite clear, and a fairly compliant Ruby implementation.

这些方法Rubinius 实现通常是我回答这些问题的来源,因为它非常清晰,并且是一个相当兼容的 Ruby 实现。

回答by jvalanen

When dealing with ActiveRecord there's a significant difference too:

处理 ActiveRecord 时也有显着差异:

dupcreates a new object without its id being set, so you can save a new object to the database by hitting .save

dup创建一个没有设置 id 的新对象,因此您可以通过点击将新对象保存到数据库中 .save

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

clonecreates a new object with the same id, so all the changes made to that new object will overwrite the original record if hitting .save

clone创建一个具有相同 id 的新对象,因此如果命中,对该新对象所做的所有更改都将覆盖原始记录 .save

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

回答by Jonathan Fretheim

One difference is with frozen objects. The cloneof a frozen object is also frozen (whereas a dupof a frozen object isn't).

一个区别在于冻结对象。在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

Another difference is with singleton methods. Same story here, dupdoesn't copy those, but clonedoes.

另一个区别在于单例方法。同样的故事在这里,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!

回答by veeresh yh

Both are nearly identical but clone does one more thing than dup. In clone, the frozen state of the object is also copied. In dup, it'll always be thawed.

两者几乎相同,但 clone 比 dup 多做一件事。在克隆中,对象的冻结状态也被复制。在 dup 中,它总是会被解冻。

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

回答by Xavier Nayrac

The newer docincludes a good example:

新的文档包含一个很好的例子:

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>

回答by Donato

You can use clone to do prototype-based programming in Ruby. Ruby's Object class defines both the clone method and dup method. Both clone and dup produce a shallow copy of the object it is copying; that is, the instance variables of the object are copied but not the objects they reference. I will demonstrate an example:

您可以使用 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"

Notice in the above example, the orange clone copies the state (that is, the instance variables) of the apple object, but where the apple object references other objects (such as the String object color), those references are not copied. Instead, apple and orange both reference the same object! In our example, the reference is the string object 'red'. When orange uses the append method, <<, to modify the existing String object, it changes the string object to 'red orange'. This in effect changes apple.color too, since they are both pointing to the same String object.

请注意,在上面的示例中,橙色克隆复制了苹果对象的状态(即实例变量),但是在苹果对象引用其他对象的地方(例如 String 对象颜色),这些引用不会被复制。相反,apple 和 orange 都引用同一个对象!在我们的示例中,引用是字符串对象“red”。当 orange 使用 append 方法 << 修改现有的 String 对象时,它将字符串对象更改为“red orange”。这实际上也改变了 apple.color,因为它们都指向同一个 String 对象。

As a side note, the assignment operator, =, will assign a new object and thus destroy a reference. Here is a demonstration:

作为旁注,赋值运算符 = 将分配一个新对象,从而破坏一个引用。这是一个演示:

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'

In the above example, when we assigned a fresh new object to the color instance method of the orange clone, it no longer references the same object as apple. Hence, we can now modify the color method of orange without affecting the color method of apple, but if we clone another object from apple, that new object will reference the same objects in copied instance variables as apple.

在上面的例子中,当我们将一个全新的对象分配给橙色克隆的颜色实例方法时,它不再引用与苹果相同的对象。因此,我们现在可以在不影响 apple 的 color 方法的情况下修改 orange 的 color 方法,但是如果我们从 apple 克隆另一个对象,该新对象将在复制的实例变量中引用与 apple 相同的对象。

dup will also produce a shallow copy of the object it is copying, and if you were to do the same demonstration shown above to dup, you will see it works exactly the same way. But there are two major differences between clone and dup. First, as others mentioned, clone copies the frozen state and dup does not. What does this mean? The term 'frozen' in Ruby is an esoteric term for immutable, which itself is a nomenclature in computer science, meaning that something cannot be changed. Thus, a frozen object in Ruby cannot be modified in any way; it is, in effect, immutable. If you attempt to modify a frozen object, Ruby will raise a RuntimeError exception. Since clone copies the frozen state, if you attempt to modify a cloned object, it will raise a RuntimeError exception. Conversely, since dup does not copy the frozen state, no such exception will occur, as we'll demonstrate:

dup 还将生成它正在复制的对象的浅拷贝,如果您对 dup 执行上面显示的相同演示,您将看到它以完全相同的方式工作。但是 clone 和 dup 之间有两个主要区别。首先,正如其他人提到的,clone 复制了冻结状态,而 dup 没有。这是什么意思?Ruby 中的术语“冻结”是不可变的深奥术语,它本身是计算机科学中的一个术语,意思是某些东西无法改变。因此,Ruby 中的冻结对象不能以任何方式修改;实际上,它是不可变的。如果您尝试修改冻结的对象,Ruby 将引发 RuntimeError 异常。由于克隆复制了冻结状态,如果您尝试修改克隆对象,它将引发 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

Second, and, more interestingly, clone copies the singleton class (and hence its methods)! This is very useful if you desire to undertake prototype-based programming in Ruby. First, let's show that indeed the singleton methods are copied with clone, and then we can apply it in an example of prototype-based programming in Ruby.

其次,更有趣的是,clone 复制了单例类(以及它的方法)!如果您希望在 Ruby 中进行基于原型的编程,这将非常有用。首先,让我们证明确实使用 clone 复制了单例方法,然后我们可以将其应用到 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 

As you can see, the singleton class of the fruit object instance is copied to the clone. And hence the cloned object has access to the singleton method :seeded?. But this is not the case with dup:

如您所见,水果对象实例的单例类被复制到克隆中。因此,克隆对象可以访问单例方法 :seed?。但这不是 dup 的情况:

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

Now in prototype-based programming, you do not have classes which extend other classes and then create instances of classes whose methods derive from a parent class that serves as a blueprint. Instead, you have a base object and then you create a new object from the object with its methods and state copied over (of course, since we are doing shallow copies via clone, any objects the instance variables reference will be shared just as in JavaScript prototypes). You can then fill in or change the object's state by filling in the details of the cloned methods. In the below example, we have a base fruit object. All fruit have seeds, so we create a method number_of_seeds. But apples have one seed, and so we create a clone and fill in the details. Now when we clone apple, we not only cloned the methods but we cloned the state! Remember clone does a shallow copy of the state (instance variables). And because of that, when we clone apple to get a red_apple, red_apple will automatically have 1 seed! You can think of red_apple as an object that inherits from Apple, which in turn inherits from Fruit. Hence, that is why I capitalized Fruit and Apple. We did away with the distinction between classes and objects courtesy of clone.

现在在基于原型的编程中,您没有类可以扩展其他类,然后创建类的实例,这些类的方法派生自作为蓝图的父类。相反,您有一个基础对象,然后您从该对象创建一个新对象,并复制其方法和状态(当然,因为我们通过克隆进行浅拷贝,所以实例变量引用的任何对象都将像在 JavaScript 中一样共享原型)。然后,您可以通过填写克隆方法的详细信息来填写或更改对象的状态。在下面的例子中,我们有一个基本的水果对象。所有的水果都有种子,所以我们创建了一个 number_of_seeds 方法。但是苹果只有一颗种子,所以我们创建了一个克隆并填写了细节。现在当我们克隆苹果时,我们不仅克隆了方法,而且克隆了状态!记住 clone 对状态(实例变量)做了一个浅拷贝。正因为如此,当我们克隆苹果以获得 red_apple 时,red_apple 将自动拥有 1 个种子!您可以将 red_apple 视为继承自 Apple 的对象,而 Apple 又继承自 Fruit。因此,这就是我将 Fruit 和 Apple 大写的原因。我们通过 clone 取消了类和对象之间的区别。

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 

Of course, we can have a constructor method in protoype-based programming:

当然,我们可以在基于原型的编程中有一个构造函数方法:

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 

Ultimately, using clone, you can get something similar to the JavaScript prototype behavior.

最终,使用 clone,您可以获得类似于 JavaScript 原型行为的东西。