From: Robert Klemme Date: 2004-06-15T00:08:37+09:00 Subject: Re: idioms for dup/clone Just some thoughs and code snippets - no general solutions here... "Paul Brannan" schrieb im Newsbeitrag news:20040614142913.GV2788@atdesk.com... > Suppose I have an class that needs to implement its own dup/clone > methods. What is the correct way to write these methods? > > One way is to create protected accessors: > > class Foo > def initialize(foo, bar) > @foo = foo > @bar = bar > end > > def dup > copy = super > copy.foo = @foo.dup > copy.bar = @bar.dup > end You need to return 'copy' here. > protected > attr_accessor :foo > attr_accessor :bar > end > > Another way is to use instance_eval: > > class Foo > def dup > copy = super > foo = @foo.dup > bar = @bar.dup > copy.instance_eval do > @foo = foo > @bar = bar > end You need to return 'copy' here. > end > end > > Questions: > > 1. How else might dup/clone be implemented? (the deep-copy trick using > marshaling is one way, but the goal here is to write a one-level-deep > dup/clone). > > 2. What is the right way to set the new instance variables in the copy? I guess it really depends... > 3. Should dup be implemented in terms of clone or should clone be > implented in terms of dup (or should they both be implemented > independently or in terms of another function)? IMHO neither since #dup and #clone do have different semantics with respect to freeze. Well, you could do something like this: class Foo attr_accessor :foo, :bar def dup; do_copy( super, :dup ); end def clone; do_copy( super, :clone ); end protected def do_copy(copy, sym) copy.instance_eval do @foo = @foo.send sym @bar = @bar.send sym end copy end end but this does not work for #clone if the instance is frozen. This might be better: class Foo attr_accessor :foo, :bar def clone; copy :clone; end def dup; copy :dup; end protected def copy(sym) c = self.class.new copy_init c c.freeze? if frozen c.taint if tainted? c end def copy_init(c) c.instance_eval do @foo = @foo.sent sym @bar = @bar.sent sym end end end class Bar < Foo attr_accessor :name protected def copy_init(c) super c.instance_eval do @name = @name.send sym end end end > 4. When making the copy, is super the right way to make the copy, or > should allocate be used? (allocate doesn't work on 1.6.x, which I > still use heavily, though if it's the right solution, then it's the > right solution). I'm inclined to follow the Java clone() pattern and use super. Of course you can also just do self.class.new. > 5. Certain types cannot be dup'd (e.g. NilClass in all versions of Ruby > or Fixnum in 1.8 and later), so the above code will break if I write: > > Foo.new(10, 42).dup > > Is it possible (or is it even wise) to write a dup or clone function > that works with both value types and container types? It depends on whether you expect these types as members and what you want to be done with them. It's difficult to anwer this generally, that's why the automatic methods just do a shallow copy. Generally speaking IMHO NilClass#dup should return self, same for Fixnums and others. But OTOH I can see why Matz did it this way: so you get to know that some instance is not cloneable. Kind regards robert