I thought of a quick addendum to Monday’s article on covariant, templatized copy constructors. The end of that article said
[T]here is a large caveat that you may have noticed. Due to the the inversion of the inheritance hierarchy around the cloning mixins, we have completely lost the ability to construct derived objects using anything but the default constructor. If you need to construct a
Derived
(which is really aCloneable<Derived_>
) and pass an argument to theDerived_
constructor, you cannot.
And then I go on to suggest using a factory pattern that is aware of the quirky type hierarchy described in that post. But in fact there is a relatively elegant way around this problem without using factories.
If you look back at the definition of the Cloneable<T>
class from Monday’s post, you will notice that its sole constructor is a “copy” constructor that is not really a copy constructor at all but an implicit conversion constructor from the wrapped type T
. This was intentional, and intended for convenience (although I also left out a true copy constructor, which was an error).
Since we can implicitly convert T
to Cloneable<T>
it should be trivial enough to construct a T
any way we like and have it magically turn into a Cloneable<T>
. The tricky part is that the pattern requires that the typedef for Cloneable<T>
be the meaningful name, and true name of the type T
be somewhat obfuscated. So it becomes inelegant to refer to T
directly.
But all is not lost. We can extend Cloneable<T>
and AbstractCloneable<T>
like this (and note that I have added the missing copy constructors).
template class AbstractCloneable : public T { public: typedef typename T construct; AbstractCloneable() {}; AbstractCloneable(const AbstractCloneable& copy) : T(copy) {} AbstractCloneable(const T& copy) : T(copy) {}; virtual ~AbstractCloneable() {}; virtual AbstractCloneable* clone() const = 0; }; template class Cloneable : virtual public AbstractCloneable { public: Cloneable() {}; Cloneable(const Cloneable& copy) : AbstractCloneable(copy) {}; Cloneable(const T& copy) : AbstractCloneable(copy) {}; virtual ~Cloneable() {}; virtual Cloneable* clone() const { return new Cloneable(static_cast(*this)); } };
The key is on line 5. Now, if we have a class with a non-default constructor like so
class Derived_ : public Base { public: Derived_(int arg) {}; Derived_(const Derived_& copy) : Base(copy) {}; }; typedef Cloneable Derived;
We don’t need a factory to make a Derived
. We can just do this
Dervied d = Derived::construct(4);
It’s not exactly the usual constructor syntax, but you are indeed accessing the constructor directly and not through a factory method.
So it turns out that the idea from Monday’s post is a lot more generally applicable than I originally thought. I think I’ll start using it myself.
Edit: This post originally ended here, but as commenter Matthias points out below, this is not quite complete. In particular, calling Derived::construct
doesn’t work because it constructs a raw Derived_
object outside of its Cloneable
wrapper. Derived_
inherits from AbstractCloneable<Base_>
. As the name implies, AbstractCloneable<Base_>
is abstract, and outside of its wrapper, the Derived_
object does not provide an implementation for the abstract clone
method in AbstractCloneable<Base_>
.
The solution is adding one more small shim, a wrapper class to be used when deriving from AbstractCloneable
classes:
template class AbstractCloneableParent : public T { protected: AbstractCloneableParent(const typename T::construct& copy) : T(copy) { }; private: virtual AbstractCloneable* clone() const { return new Cloneable(*this); } }; class Derived_ : public AbstractCloneableParent { public: Derived_(int arg) {}; Derived_(const Derived_& copy) : AbstractCloneableParent(copy) {}; };
(If you find the use of T::construct
above to be confusing, you could always add a second typedef in AbstractCloneable
that calls the base class something that seems more intuitive in this kind of circumstance.)
This provides an implementation of clone()
in Derived_
for the sole purpose of appeasing the compiler in order to allow the creation of temporary Derived_
objects. Keep in mind that the Derived_
class is internal, and is not intended to be used by consumers of this class hierarchy, so the only time that a raw Derived_
object should exist is temporarily when invoking Derived::construct
to create a Derived
object.
Note that the implementation of clone
in AbstractCloneableParent
is private. That is because in all cases invoking this implementation is an error. It is a function called “clone” which does not clone at all, and in fact slices the object. Therefore we want to be sure that if someone ever does create a Derived_
object that at least they will get an error if they try to call clone()
on it, rather than having it behave in an unintuitive manner. Granted, one could still access that clone()
implementation through a Base*
that points to a raw Derived_
object, but in the end there’s only so much you can do to protect people from themselves.
Share this content on:
How is it possible to initialize base classes that take a parameter from the derived classes? Here is an example.
This is the compiler error message (tested with g++ 4.3 and 4.5):
As the compiler said, you’re only allowed to pass arguments to the constructor of your immediate base class(es), and not other ancestor classes. One way to get the effect you’re looking for is like this:
What this does is use the
Base_
constructor (aliased byBase::construct
) to create a temporaryBase_
object with the given parameter. Then, that object is passed to the copy constructor ofBase
(akaAbstractCloneable<Base_>
) which in turn passes it on to the copy constructor of theBase_
class within theDerived_
object.This requires creating a temporary object, so that’s something to look out for if your classes allocate a lot of memory or consume other resources, or if they have unusual copy semantics, but this should work for most cases.
The only case in which this definitely will not work is if
Base_
contains pure virtual functions and cannot be directly instantiated. If this is case, you are probably stuck with using a factory pattern. However, a situation in whichBase_
would both accept a constructor parameter and also be an abstract class would seem uncommon.Thanks for your comment, Tyler. I know see what’s going on in terms of object copying and the constructors in Cloneable and AbstractCloneable make sense to me. I believe, though, that
AbstractCloneable(const AbstractCloneable& copy) : T(copy) {}
and
Cloneable(const Cloneable& copy) : AbstractCloneable(copy) {}
are not needed (at least not in my example). Is there a particular reason why you provide these copy constructors?
Also, I am having still trouble understanding why the compiler complains about pure functions in my example:
cloneable2.cc:68: error: cannot allocate an object of abstract type ‘derived_’
cloneable2.cc:41: note: because the following virtual functions are pure within ‘derived_’:
cloneable2.cc:12: note: abstract_cloneable* abstract_cloneable::clone() const [with T = base_]
Do you have any ideas on that issue?
No, those constructors are not technically needed. Well, they are needed, but they are created implicitly if they are omitted from the code. As a matter of style, though, I try to avoid using implicit constructors except for very simple classes.
As for the error you posted, that is a legitimate bug in my implementation. I have added an addendum to the main post which provides a fix for this.
You will have to modify the method of passing the argument to the base class accordingly since the direct parent of
Derived_
will now beAbstractCloneableParent<Base>
instead ofBase
itself, but the same principle still applies.Thanks for the addendum, Tyler, my toy example works fine now!