【问题标题】:Provide simplest example where deep copy is needed in ruby提供在 ruby​​ 中需要深拷贝的最简单示例
【发布时间】:2016-08-30 13:07:17
【问题描述】:

我真的不明白浅拷贝和深拷贝的区别。 Ruby 的#dup 在我测试时似乎创建了一个深拷贝。

文档说:

Produces a shallow copy of obj---the instance variables of obj are
copied, but not the objects they reference.

但是当我对此进行测试时,它似乎改变了它们引用的对象。

class Klass
  attr_accessor :name
end

a = Klass.new
a.name = "John"
b = a.dup
b.name = "Sue"
puts a.name # John

@nameobjects they reference 之一时,为什么这里的浅拷贝就足够了?
需要深拷贝的最简单示例是什么?

【问题讨论】:

    标签: ruby deep-copy shallow-copy


    【解决方案1】:

    您展示的示例没有描述深拷贝和浅拷贝之间的区别。相反,请考虑以下示例:

    class Klass
      attr_accessor :name
    end
    
    anna = Klass.new
    anna.name = 'Anna'
    
    anna_lisa = anna.dup
    anna_lisa.name << ' Lisa'
    # => "Anna Lisa"
    
    anna.name
    # => "Anna Lisa"
    

    通常,dupclone 都应该只是复制您正在调用该方法的实际对象。没有其他引用对象,如上面示例中的 name String 重复。因此,在复制之后,原始对象和复制对象都指向同一个名称字符串。

    对于deep_dup,通常所有(相关)引用的对象也会被复制,通常是无限的深度。由于这对于所有可能的对象引用都很难实现,因此人们通常依赖于特定对象的实现,例如哈希和数组。

    一个相当通用的 deep-dup 的常见解决方法是使用 Ruby 的 Marshal 类来序列化对象图并再次直接反序列化它。

    anna_lena = Marshal.load( Marshal.dump(anna))
    

    这会创建新对象并且实际上是 deep_dup。由于大多数对象立即支持编组,这是一个相当强大的机制。请注意,您永远不应解组(即load)用户提供的数据,因为这将导致远程代码执行漏洞。

    【讨论】:

    • 你是对的,我没有更改字符串,只是在我的示例中重新分配了它。
    【解决方案2】:

    试试这个:

    class Klass
      attr_accessor :name
    end
    
    a = Klass.new
    a.name = Klass.new #object inside object
    a.name.name = 'George'
    b = a.dup
    puts b.name.name # George
    
    b.name.name = 'Alex'
    puts a.name.name # Alex
    

    还要注意(see info):

    使用 dup 时,对象已扩展的任何模块 不会被复制。

    编辑:关于字符串的注释(这很有趣) 字符串在原始场景中被引用而不是被复制。通过这个案例证明了这一点:

    a.name = 'George'
    puts a.name.object_id # 69918291262760    
    
    b = a.dup
    puts b.name # George
    puts b.name.object_id # 69918291262760  
    
    b.name.concat ' likes tomatoes' # append to existing string
    puts b.name.object_id # 69918291262760  
    
    puts a.name # George likes tomatoes
    

    这按预期工作。引用的对象(包括字符串)不会被复制,而是共享引用。

    那么为什么原来的例子没有出现呢?这是因为当您将 b.name 设置为不同的内容时,您将其设置为新字符串。

       a.name = 'hello' 
    

    这确实是简写:

       a.name = String.new('hello')
    

    因此在原始示例中,a.name & b.name 不再引用同一个对象,您可以查看 object_id 来查看。

    请注意,Fixnum、浮点数、真、假或符号不是这种情况。这些对象在浅拷贝中被复制。

    【讨论】:

    • 优秀的简洁示例,但为什么这会使#dup 失败?这让我很困惑。
    • 这应该有助于解释字符串对象的特殊情况。与不是“真实”对象的固定数字和符号不同,它们就像它们一样,String 有一些特殊的方法来节省空间。它们既不是“普通”对象,也不是像 fixnums 这样的假对象:patshaughnessy.net/2012/1/18/…
    • 谢谢。字符串的写时复制优化很有趣,但与此无关。
    • @ABrowne 在您的字符串示例中,您仍在分配一个新字符串。因此,最终a.name 你仍然是"George"+= 运算符不会修改对象,而是将添加的结果分配为新对象。
    • @HolgerJust 好点,将其换成更适合我的示例的操作。谢谢
    猜你喜欢
    • 2012-04-13
    • 1970-01-01
    • 1970-01-01
    • 2012-01-26
    • 1970-01-01
    • 1970-01-01
    • 2011-03-22
    • 2013-09-09
    • 1970-01-01
    相关资源
    最近更新 更多