Can I have your feedback on this general mix-in pattern?
Hi,
I needed to implement cloning and saw that I ended up with copy and
paste all over the code. I thought that this should be able to be solved
with mix-ins. After research and help from you (Is the Mixin pattern
accepted in all camps? 6 July) I thought that I probably would find a
good solution to this.
I found a nice first approach at Alf P. Steinbach's blog.
http://alfps.wordpress.com/2010/06/12/cppx-3-ways-to-mix-in-a-generic-cloning-implementation/
The approach that caught my interest was Way #2: Mix-in via a middleman
base class.
However, I thought the syntax would be slightly verbose using it
straight out of the box, and since I'm a perfectionist I tried to find a
solution where I could pack mix-ins together.
In Andrei Alexandrescu's library Loki he presents type list as a way to
pack types.
http://www.drdobbs.com/184403813
The problem, though, is that I don't want to pack types, but templates.
I have now worked on the problem and think I've found something quite
powerful.
The general mix-in pattern idea builds on C++0x and looks like this.
All mix-ins must conform to the following interface.
template<class T, class Base>
class SomeCustomMixin : public Base
{
public:
// Pass on all constructor arguments to the Base.
// If you don't want to be able to pass arguments to the Base you
// can remove this.
template<typename... Arguments>
inline SomeCustomMixin( Arguments&&... arguments )
: Base( std::forward<Arguments>( arguments )... ) {}
/* Implement your mix-in here. */
};
-------------------------------------------------
This is what the mix-in list looks like. This is not a /type/ list but
rather a /template/ list.
//file NullType.h
#ifndef NULL_TYPE_H_
#define NULL_TYPE_H_
class NullType {};
#endif
-----------------------
// file MixinList.h
#ifndef MIXIN_LIST_H_
#define MIXIN_LIST_H_
#include "NullType.h"
// This is needed to circumvent unimplemented parts in gcc.
template<template<class, class> class... Rest>
struct MixinList;
template
<
template<class, class> class Mixin,
template<class, class> class... Rest
struct MixinList<Mixin, Rest...>
{
// This is a trick to simulate 'using Head = Mixin;'
template<class T, class Base>
class Head : public Mixin<T, Base>
{
public:
// Pass on all constructor arguments to the Base.
// If you don't want to be able to pass arguments to the Base you
// can remove this.
template<typename... Argument>
inline Head( Argument&&... arguments )
: Mixin<T, Base>( std::forward<Arguments>( arguments )... )
{
}
};
// When template aliasing comes to gcc we can use
// using Head = Mixin;
typedef MixinList<Rest...> Tail;
};
// Cap the MixinList recursion with a specialization using
// NullType as Tail.
template
<
template<class, class> class Mixin
struct MixinList<Mixin>
{
template<class T, class Base>
class Head : public Mixin<T, Base>
{
public:
// Pass on all constructor arguments to the Base.
// If you don't want to be able to pass arguments to the Base you
// can remove this.
template<typename... Arguments>
inline Head( Arguments&&... arguments )
: Mixin<T, Base>( std::forward<Arguments>( arguments )... )
{
}
};
typedef NullType Tail;
};
#endif
----------------
Now comes the actual Mixins template that applies all the mix-ins to a
class.
//file EmptyType.h
#ifndef EMPTY_TYPE_H_
#define EMPTY_TYPE_H_
struct EmptyType {};
#endif
-----------------------
// file Mixins.h
#ifndef MIXINS_H_
#define MIXINS_H_
#include "EmptyType.h"
#include "NullType.h"
template<class T, class MixinList, class Base = EmptyType>
class Mixins : public MixinList::template Head<T,
Mixins<T, typename MixinList::Tail, Base>>
{
public:
// Pass on all constructor arguments to the Base.
// If you don't want to be able to pass arguments to the Base you
// can remove this.
template<typename... Arguments>
inline Mixins( Arguments&&... arguments )
: MixinList::template Head<T,
Mixins<T, typename MixinList::Tail, Base>>(
std::forward<Arguments>( arguments )... )
{
}
};
// When MixinList is NullType, end the recursion with a specialization.
template<class T, class Base>
class Mixins<T, NullType, Base> : public Base
{
public:
// Pass on all constructor arguments to the Base.
// If you don't want to be able to pass arguments to the Base you
// can remove this.
template<typename... Arguments>
inline Mixins( Arguments&&... arguments )
: Base( std::forward<Arguments>( arguments )... )
{
}
};
#endif
------------
Or, when template aliasing comes to gcc we may be able to use this file
instead.
// file Mixins.h
#ifndef MIXINS_H_
#define MIXINS_H_
#include "EmptyType.h"
#include "NullType.h"
template<class T, class MixinList, class Base = EmptyType>
using Mixins = MixinList::Head<T,
Mixins<T, typename MixinList::Tail, Base>>;
// End the recursion with a specialization using NullType.
template<class T, class Base = EmptyType>
using Mixins<T, NullType, Base> = Base;
#endif
-----------------
That's it! Let's use it. All code here is written using gcc 4.5.0.
First we create a mix-in called CloneMixin.
// file CloneMixin.h
#ifndef CLONE_MIXIN_H_
#define CLONE_MIXIN_H_
#include <memory>
#include <assert.h>
template<class T, class Base>
class CloneMixin : public Base
{
public:
typedef std::unique_ptr<T> CloneUPtr;
// Pass on all constructor arguments to the Base.
// If you don't want to be able to pass arguments to the Base you
// can remove this.
template<typename... Arguments>
inline CloneMixin( Arguments&&... arguments )
: Base( std::forward<Arguments>( arguments )... ) {}
virtual ~CloneMixin() {}
// Use NVI to be able to do an important assert.
inline CloneUPtr clone() const
{
CloneUPtr c( static_cast<T*>(clone_()) );
assert( typeid(*c.get()) == typeid(*this) );
return c;
}
protected:
virtual CloneMixin* clone_() const
{
return new T( *static_cast<const T*>(this) );
}
};
#endif
-------------------------------------
Let's make another mix-in.
// file TypeNameMixin.h
#ifndef TYPE_NAME_MIXIN_H_
#define TYPE_NAME_MIXIN_H_
template<class T, class Base>
class TypeNameMixin : public Base
{
public:
// Pass on all constructor arguments to the Base.
// If you don't want to be able to pass arguments to the Base you
// can remove this.
template<typename... Arguments>
inline TypeNameMixin( Arguments&&... arguments )
: Base( std::forward<Arguments>( arguments )... ) {}
virtual ~TypeNameMixin() {}
virtual std::string getTypeName() const
{
// This could be changed to something of your own taste.
// Maybe the mix-in could be initialized with a more convenient
// type name.
return typeid(T).name();
}
};
#endif
----------------------------------------
And here's the main program.
class MyBaseClass : public Mixins<MyBaseClass,
MixinList<CloneMixin, TypeNameMixin>>
{
};
class MyDerivedClass : public Mixins<MyDerivedClass,
MixinList<CloneMixin, TypeNameMixin>,
MyBaseClass>
{
};
int main()
{
MyDerivedClass dc;
std::unique_ptr<MyBaseClass> bc = dc->clone();
assert( typeid(*bc.get()) == typeid(dc) );
std::cout << dc.getTypeName() << std::endl;
std::cout << bc->getTypeName() << std::endl;
return 0;
}
I would really appreciate your feedback, both if you find flaws, but
also if you do not find flaws (or even find it usable).
I have two questions myself though:
1. The clone() function will be overridden in each derived class using
the CloneMixin. In C++ FAQ Lite they mention something about this, but
what's your opinion about overriding non-virtual for mix-in purposes?
http://www.parashift.com/c++-faq-lite/strange-inheritance.html#faq-23.8
2. To be able to store templates in the template list MixinList without
template aliasing that doesn't exist yet for gcc, I use a special inner
template trick for Head. Do you see any problems with that trick?
Thanks,
Daniel
--
[ See http://www.gotw.ca/resources/clcm.htm for info about ]
[ comp.lang.c++.moderated. First time posters: Do this! ]