Just some thoughs and code snippets - no general solutions here...
"Paul Brannan" <pbrannan / atdesk.com> 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