I have the following hierarchy pattern in various places in the codebase:
enum DerivedType {
A, B, C };
class Base {
public:
static Base* Create(DerivedType t);
};
template <DerivedType T>
class Derived : public Base {
};
The Create
method returns a new object of class Derived<A>
, Derived<B>
, or Derived<C>
, depending on its argument:
Base* Base::Create(DerivedType t) {
switch (t) {
case A: return new Derived<A>;
case B: return new Derived<B>;
case C: return new Derived<C>;
default: return NULL;
}
}
The problem is that there are many such Base -> Derived
hierarchies, with essentially the same implementation of Create()
copy-pasted all over the place. Is there an elegant, yet easy-to-understand way to avoid duplication here?
We can abstract away the Factory details, and rely on the client to provide us the mapping of enum to class type:
template<typename ENUM, typename T>
struct Factory
{
typedef std::map<ENUM, T*(*)()> map_type;
static map_type factoryMapping_;
static T* Create(ENUM c)
{
return factoryMapping_[c]();
}
static void Init(map_type _mapping)
{
factoryMapping_ = _mapping;
}
};
template<typename ENUM, typename T>
typename Factory<ENUM, T>::map_type Factory<ENUM,T>::factoryMapping_;
Now it's the client's job to provide us methods for creating a Base*
given some enum value.
If you're willing and able to abstract away the creation of a derived class with a template, then you can save a fair bit of typing.
What I mean is, let's create a templated function to create a derived class (without any real checking for correctness):
template<typename Base, typename Derived>
Base* CreateDerived()
{
return new Derived();
}
Now I can define an enum and associated class hierarchy:
enum ClassType {A, B};
struct Foo
{
virtual void PrintName() const
{
std::cout << "Foo\n";
}
};
typedef Factory<ClassType, Foo> FooFactory ;
struct DerivedOne : public Foo
{
virtual void PrintName() const
{
std::cout << "DerivedOne\n";
}
};
struct DerivedTwo : public Foo
{
virtual void PrintName() const
{
std::cout << "DerivedTwo\n";
}
};
And then use it like so:
// set up factory
std::map<ClassType, Foo*(*)()> mapping;
mapping[A] = &CreateDerived<Foo, DerivedOne>;
mapping[B] = &CreateDerived<Foo, DerivedTwo>;
FooFactory::Init(mapping);
// Use the factory
Foo* f = FooFactory::Create(A);
f->PrintName();
Of course this simplifies your problem a bit, namely moving the factory details out of the base class and ignoring for a minute that the children themselves are templated. Depending on how hard it is in your domain to create a good CreateDerived
function for each type, you may not end up saving a ton of typing.
EDIT: We can modify our Create
function to return NULL by taking advantage of std::map::find. I omitted it for brevity. If you concerned about performance, then yes, an O(log n) search is slower asymptotically than a simple switch, but I strongly doubt this will wind up being the hot path.
You could make it a template
template<typename Base, typename Derive, template<Derive> class Derived>
Base* Base::Create(Derive t) {
switch (t) {
case Derive::A: return new Derived<Derive::A>;
case Derive::B: return new Derived<Derive::B>;
case Derive::C: return new Derived<Derive::C>;
default: return nullptr;
}
}
but that assumes that there are only ever A
, B
, and C
in struct enum Derive
.
Instead of using a C++ style enum you could use a Java style enum where each value is a singleton of a class derived from a common base.
Then define a set of pure virtual functions on the base class that create the desired flavour of derived class, and implement them appropriately in each of the singletons.
Then instead of switch (t) ...
you can use t->createDerived()
.
Or to put it more succinctly: replace switch with polymorphism.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With