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 a Derived (which is really a Cloneable<Derived_>) and pass an argument to the Derived_ 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:

Facebooktwittergoogle_plusredditpinterestlinkedinmailFacebooktwittergoogle_plusredditpinterestlinkedinmail

6 comments

  1. How is it possible to initialize base classes that take a parameter from the derived classes? Here is an example.

    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):

    cloneable2.cc: In constructor 'derived_::derived_()':
    cloneable2.cc:44: error: type 'base_' is not a direct base of 'derived_'
    cloneable2.cc: In constructor 'derived_::derived_(int)':
    cloneable2.cc:46: error: type 'base_' is not a direct base of 'derived_'
    cloneable2.cc: In function 'int main()':
    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_]
    cloneable2.cc:69: error: cannot allocate an object of abstract type 'derived_'
    cloneable2.cc:41: note:   since type 'derived_' has pure virtual functions
    cloneable2.cc:68: warning: unused variable 'd'
    cloneable2.cc:69: warning: unused variable 'e'
    cloneable2.cc: In constructor 'abstract_cloneable::abstract_cloneable() [with T = base_]':
    cloneable2.cc:44:   instantiated from here
    cloneable2.cc:7: error: no matching function for call to 'base_::base_()'
    cloneable2.cc:35: note: candidates are: base_::base_(const base_&)
    cloneable2.cc:34: note:                 base_::base_(int)
    
  2. 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:

    Derived_::Derived_(int i)
      : Base(Base::construct(i))
    {}
    

    What this does is use the Base_ constructor (aliased by Base::construct) to create a temporary Base_ object with the given parameter. Then, that object is passed to the copy constructor of Base (aka AbstractCloneable<Base_>) which in turn passes it on to the copy constructor of the Base_ class within the Derived_ 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 which Base_ would both accept a constructor parameter and also be an abstract class would seem uncommon.

  3. 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?

  4. 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 be AbstractCloneableParent<Base> instead of Base itself, but the same principle still applies.

Leave a Reply to Matthias Vallentin Cancel reply

Your email address will not be published. Required fields are marked *