No Factories Necessary
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 aDerived(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.
template <typename T> class abstract_cloneable : public T { public: typedef T construct; abstract_cloneable() { } //abstract_cloneable(const abstract_cloneable<T>& copy) : T(copy) { } abstract_cloneable(const T& copy) : T(copy) {} virtual ~abstract_cloneable() { } virtual abstract_cloneable<T>* clone() const = 0; }; template <typename T> class cloneable : virtual public abstract_cloneable<T> { public: cloneable() { } //cloneable(const cloneable<T>& copy) : abstract_cloneable<T>(copy) { }; cloneable(const T& copy) : abstract_cloneable<T>(copy) { }; virtual ~cloneable() { } virtual cloneable<T>* clone() const { return new cloneable<T>(static_cast<const T&>(*this)); } }; class base_ { protected: base_(int i) { } base_(const base_& copy) { } }; typedef abstract_cloneable<base_> base; class derived_ : public base { public: // Does not work, T (base_) is not a direct base of derived_. derived_() : base::construct(0) { } derived_(int i) : base::construct(i) { } protected: derived_(const derived_& copy) : base(copy) { } }; typedef cloneable<derived_> derived; class derived2_ : public derived { public: derived2_() : derived() { } protected: derived2_(const derived2_& copy) : derived(copy) { } }; typedef cloneable<derived2_> derived2; int main() { derived d = derived::construct(); derived e = derived::construct(42); return 0; }This is the compiler error message (tested with g++ 4.3 and 4.5):Derived_::Derived_(int i) : Base(Base::construct(i)) {}What this does is use theBase_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 ifBase_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.Derived_will now beAbstractCloneableParent<Base>instead ofBaseitself, but the same principle still applies.