On Fri, Oct 16, 2009 at 12:30 PM, Caleb Clausen <vikkous / gmail.com> wrote:

> Object#dup does not call new; I think it's more like:
> self.class.allocate.initialize_copy(self). See what happens here:
>
> irb(main):001:0> class K
> irb(main):002:1> def initialize
> irb(main):003:2> p :initialize
> irb(main):004:2> end
> irb(main):005:1> end
> => nil
> irb(main):006:0> k=K.new
> :initialize
> => #<K:0xb7ce8ee0>
> irb(main):008:0> k2=k.dup
> => #<K:0xb7ce0f38>

And clone doesn't call initialize EITHER:

class A
  def initialize(iv)
    @iv = iv
    puts "initialize called"
  end

  def initialize_copy(arg)
    puts "initialize copy called, my iv is #{@iv}"

  end
end

puts "Creating original"
a = A.new(42)
puts "calling dup"
a1 = a.dup
puts "calling clone"
a2 = a.clone

outputs

Creating original
initialize called
calling dup
initialize copy called, my iv is 42
calling clone
initialize copy called, my iv is 42

It you look at the source code in object.c It becomes apparent that
Object#dup and Object#clone do pretty much the same thing except for
propagating the frozen bit and singleton classes:

VALUE
rb_obj_clone(obj)
    VALUE obj;
{
    VALUE clone;

    if (rb_special_const_p(obj)) {
        rb_raise(rb_eTypeError, "can't clone %s", rb_obj_classname(obj));
    }
    clone = rb_obj_alloc(rb_obj_class(obj));
    RBASIC(clone)->klass = rb_singleton_class_clone(obj);
    RBASIC(clone)->flags = (RBASIC(obj)->flags | FL_TEST(clone,
FL_TAINT)) & ~(FL_FREEZE|FL_FINALIZE);
    init_copy(clone, obj);
    RBASIC(clone)->flags |= RBASIC(obj)->flags & FL_FREEZE;

    return clone;
}

VALUE
rb_obj_dup(obj)
    VALUE obj;
{
    VALUE dup;

    if (rb_special_const_p(obj)) {
        rb_raise(rb_eTypeError, "can't dup %s", rb_obj_classname(obj));
    }
    dup = rb_obj_alloc(rb_obj_class(obj));
    init_copy(dup, obj);

    return dup;
}
static void
init_copy(dest, obj)
    VALUE dest, obj;
{
    if (OBJ_FROZEN(dest)) {
        rb_raise(rb_eTypeError, "[bug] frozen object (%s) allocated",
rb_obj_classname(dest));
    }
    RBASIC(dest)->flags &= ~(T_MASK|FL_EXIVAR);
    RBASIC(dest)->flags |= RBASIC(obj)->flags & (T_MASK|FL_EXIVAR|FL_TAINT);
    if (FL_TEST(obj, FL_EXIVAR)) {
	rb_copy_generic_ivar(dest, obj);
    }
    rb_gc_copy_finalizer(dest, obj);
    switch (TYPE(obj)) {
      case T_OBJECT:
      case T_CLASS:
      case T_MODULE:
	if (ROBJECT(dest)->iv_tbl) {
	    st_free_table(ROBJECT(dest)->iv_tbl);
	    ROBJECT(dest)->iv_tbl = 0;
	}
	if (ROBJECT(obj)->iv_tbl) {
	    ROBJECT(dest)->iv_tbl = st_copy(ROBJECT(obj)->iv_tbl);
	}
    }
    rb_funcall(dest, id_init_copy, 1, obj);
}


This code is from 1.8.6 just cuz that's what I happened to grab.

In both cases the same subroutine is used to create the state of the
new object prior to calling intialize_copy and that subroutine
basically allocates the new object, copies instance variables "under
the table" and then invokes initialize_copy, no initialize method is
ever called on the result object.

Which makes me thing that the whole "+dup+ typically uses the  class
of the descendent object to create the new instance" is meaningless,
or untrue.  Probably this is a vestige of an older implementation.


-- 
Rick DeNatale

Blog: http://talklikeaduck.denhaven2.com/
Twitter: http://twitter.com/RickDeNatale
WWR: http://www.workingwithrails.com/person/9021-rick-denatale
LinkedIn: http://www.linkedin.com/in/rickdenatale