Topic: On clone() and deriving from template of self


Author: "Marc-Andri lafortune" <lafortne@math.Princeton.EDU>
Date: 1998/02/06
Raw View
Before posting this question, I went to the book store and bought a copy of
Stroustrup's 'Desing and Evolution of C++' ("D&E"). It was interesting, but
still left me with many unanswered questions. I hope the following 2
questions/reflexions are not too long, technical or obscurely written. I
believe there are not answered in the FAQ nor the Stroustrup's C++ The C++
prog. lang. 3rd edition ("C++PL").

A) Why aren't objects cloneable?

  One of my first surprise with C++ was the absence of cloning. In C, you can
always make a copy of any type (even if reference through a pointer). Because
of inheritance, this is no longer possible without risk of slicing.
  I don't quite see why classes wouldn't have a default clone() defined;  a
class could modify it, or forbid it by making it private. It looks to me as
pretty similar to the default copy constructor. This is probably
over-simplistic, but I'm still surprised there is no mention of cloning (as a
built-in/default operator) in D&E. Am I missing something basic that makes the
decision of not having a clone trivial? Maybe I'm just the only one to think
99% of the virtual classes should have a clone() defined?

B) How can we make classes cloneable? (or referencable, storable, ...)
 I personnaly define my cloning functions as returning auto_ptr, but some
people seem to dislike them; for simplifcation purposes, let's try to return
straight *s. Also, I have a Clone and a DeepClone, but I'll forget about Deep
Cloning for now.
  Please note that the problem of cloning is meant as an illustration only; I
face the same problems for other features (like being storable, ...) which
clearly don't belong in the language.

1) First, it is important to remark that

  template<class T>
  T *Clone(const T & obj)  {  return new T(obj); }

doesn't work for derived virtual classes:

  struct Base {
    virtual ~Base() {}  // is virtual
    //...
  };

  struct Derived: Base{
    //...
  };

  Base *ptr (new Derived());

Clone(*ptr) returns a pointer to an object of type Base and not a copy of the
original Derived object.

2) Well, you simply have to specialize your Clone template for Base like this:

  struct Base {
    virtual ~Base() {}  // is virtual
    //...
    virtual Base *Clone() const  {  return new Base(*this); }
  };

  template<> Base *Clone(const Base &obj)  {
    return obj.Clone();
  }

  struct Derived: Base{
    //...
    virtual Derived *Clone() const  { return new Derived(*this); }
  };

3) This probably works somehow. I think this solution is unsatisfactory. It
may seem like it entails simply to write one line for each derived class (plus
one specialization per base class). But what about DeepClone? What about more
complex behaviours where we want to add more than one or two functionalities?
It should be clear that what we are doing here is simply copy-pasting features
for each of our classes (with eventual editing). Could we use templates to
automate this process?
Let me look at what I call the "behaviour template" approach.
I call a "behaviour template" any template designed to be use like:

  struct SomeClass: Behaviour<SomeClass> {  // This add a Behaviour
                                            // to SomeClass
    // ...
  };

Could this be the solution?

  template<class T>
  struct Cloneable {
    virtual T* Clone() const  {
      return new T(static_cast<const T&>(*this));
      // Here we assumed that T will derive from Cloneable<T>!
    }
    //...
  };

  struct Base: Cloneable<Base> {
    virtual ~Base() {}
    // ...
  }

Indeed, all functions of Cloneable are available to Base. But this doesn't
work for Derived:

  struct Derived: Cloneable<Derived>, Base {  /* ... */ };

because Derived().Clone() is ambiguous. Note that it would not be so if there
was an argument allowing the compiler to pick clearly which Clone, but in our
case, or in the case of a typedef, there is no solution (I'm disregarding an
explicit Cloneable<Derived>::Clone() because that clearly would be more
complicated than solution 2)

Opinion)
I was very excited when I realized it was allowed to declare "behaviour"
templates (it's probably somewhere in the C++PL, but in D&E, it takes a single
paragraph! (p. 357)). I think it's too bad that in many cases they can serve
only for base classes. If I was to write my own ++C compiler (i.e. a better
C++), I would put these behaviour templates in an intermediate layer between
the class itself and the inherited classes; they could be overriden by the
former, but would override the latter.

4) There is a way to recreate that intermediate layer but it is pretty nasty:

  template<class T, class parent>
  struct Cloneable: parent {  // Note: we derive Cloneable from the
                // Inherited class parent
                // so that this Clone superseedes the parent's.
   typedef Cloneable Inherited;
    virtual T* Clone() const  {
      return new T(static_cast<const T&>(*this));
      // Here we assumed that T will derive from Cloneable<T>!
    }
    Cloneable(const Inherited &data) : Inherited(data) {}
  };

  struct Derived: Cloneable<Derived, Base>{
    Derived(int arg1, int arg2): Inherited(Base(arg1, arg2));
    // this used to be:  Derived(int arg1, int arg2): Base(arg1, arg2);
  };

But this is ugly, unclear for anyone not familiar with the 'hack', complicates
the creators and moreover can only be used for one behaviour (just for the
kick I tried to chain the behaviours like
  struct Derived:
    BehaviourA < Derived,
    BehaviourB < Derived,
    Cloneable  < Derived, Base>>> {};
but that's (very understandably) not compilable)

5) The last possible approach I thought of is to duplicate all the classes,
with only the copy having the added behaviours. For example:

  struct Base_imp {
    virtual ~Base_imp() {}
    //...
  }

  struct Base:  Cloneable<Base>,
                OtherBehaviour<Base>,
                Base_imp {
    // recopy all constructors of Base_imp if desired
  }

  struct Derived_imp: Base_imp {
    // ...
  };

  struct Derived:  Cloneable<Derived>,
                 OtherBehaviour<Derived>,
                 Derived_imp {
      // recopy constructors of Derived_imp
  }

I believe this should actually work with some (major) drawbacks. You have to
recopy the constructors (or else you'll be constructing stuff like
Base(Base_imp(arg1, arg2)) ). Even more serious I believe is the fact that you
cannot access any non-virtual functionnality from within the implementation
classes (Base_imp and Derived_imp), including typedefs.

5) The point I'm trying to get at is: I know of no (nice & easy) way to add
'behaviours' to virtual classes using classes or templates. I kept the only
other way for the end. It doesn't allow overriding, but it definitely is the simplest:

  #define _cloneable(T) \
    T* Clone() const  {  return new T(*this); }

  struct Derived: Base {
    _cloneable(Derived)
    // ...
  }

Here I'll quote Stroustrup (C++PL, p. 160) "Almost every macro demonstrates a
flaw in the programming language, in the program, or in the programmer"  (in
this case, I sincerely hope I'm missing something and that it's the third case)
---
[ comp.std.c++ is moderated.  To submit articles, try just posting with ]
[ your news-reader.  If that fails, use mailto:std-c++@ncar.ucar.edu    ]
[              --- Please see the FAQ before posting. ---               ]
[ FAQ: http://reality.sgi.com/austern/std-c++/faq.html                  ]