Reputation: 351
I frequently come across this annoying quirk.
Say I have something like
enum class Thing {Ex1, Ex2, Ex3}
I have an instance of a Thing and want to create a certain class depending on what it is, and it ends up looking like this
switch(_thing){
case Thing::Ex1:
ptr = new Class1();
break;
case Thing::Ex2:
ptr = new Class2();
break;
case Thing::Ex3:
ptr = new Class3();
break;
}
And so on. With 10-20 such statements, it just looks very bad. Is this an inevitability, or is there some way to improve this?
Upvotes: 2
Views: 132
Reputation: 10064
If the number of items gets large, it makes sense to create an association (aka map
) between the "Things" and factory methods. This is easy in say Python, but can be a bit of a pain in C++.
typedef Baseclass * (*factoryMethod) (void);
// factories contains the association between types and constructors
std::map <std::string, factoryMethod> factories;
// If possible, it's often a good idea to make these methods static members of Baseclass.
Baseclass * factoryMethodA(void)
{
return new ClassA();
}
Baseclass * factoryMethodB(void)
{
return new ClassB();
}
// &ct
my_map["A"] = factoryMethodA;
my_map["B"] = factoryMethodB;
// &ct
And usage is just...
ptr = factories[classname]();
If you are using C++11, I you can save a lot of typing with lambdas, to the point that this is a pretty useful programming tool to have in your belt.
#include <iostream>
#include <map>
class BaseClass {
public:
virtual void Print() = 0;
};
class ClassA : public BaseClass {
public:
void Print() { std::cout << "Hello "; }
};
class ClassB : public BaseClass {
public:
void Print() { std::cout << "World!\n"; }
};
typedef BaseClass * (*factoryMethod) (void);
std::map <std::string, factoryMethod> factories = {
{ "a", []()->BaseClass * {return new ClassA(); } },
{ "b", []()->BaseClass * {return new ClassB(); } }
};
int main (void)
{
BaseClass * foo = factories["a"]();
BaseClass * bar = factories["b"]();
foo->Print();
bar->Print();
return 0;
}
This lets you do some neat tricks, like being able to use things other than enums since you're no longer bound to switch
. Also, it lets you handle different logical associations with the same code (comes up in parsers).
PS I didn't use enums in the above code, but the idea is the same. In my experience, whenever I encountered this problem it was easier to associate strings directly with the intended behavior, so I stuck with that.
Upvotes: 2
Reputation: 11504
There is a codegenerator hidden in C++ called Template Metaprogramming. You may use it to generate factories you want. You may also use Variadic Templates (that is C++11 feature).
I came to following code (I'm a bit lazy to implement correct "last" specialized createThing
, so I used void
as terminator):
enum class Thing {Ex1, Ex2, Ex3};
class Class1 { virtual void f() {} };
class Class2 : public Class1 {};
class Class3 : public Class2 {};
template <Thing val_, typename Class_>
struct MPLFactoryPair {
static constexpr Thing val = val_;
using Class = Class_;
};
template<class head, typename... tail>
struct MPLFactory {
static Class1* createThing(Thing thing_) {
if(thing_ == head::val) {
return new typename head::Class();
}
return MPLFactory<tail...>::createThing(thing_);
}
};
template<typename last>
struct MPLFactory<last> {
static Class1* createThing(Thing thing_) {
return nullptr;
}
};
using ThingFactory =
MPLFactory<MPLFactoryPair<Thing::Ex1, Class1>,
MPLFactoryPair<Thing::Ex2, Class2>,
MPLFactoryPair<Thing::Ex3, Class3>,
void>;
The main problem with it is that you should hope that compiler will optimize tail call in createThing
. GCC does that with -O2:
<+20>: test %eax,%eax
<+22>: je 0x400a5e <main(int, char**)+238>
<+28>: cmp $0x1,%eax
<+31>: je 0x400a83 <main(int, char**)+275>
<+37>: cmp $0x2,%eax
<+40>: jne 0x400a77 <main(int, char**)+263>
So it came to simple if-else. Not sure if it will generate jump table.
Here is a test code:
int main(int argc, char* argv[]) {
volatile Thing thing = Thing::Ex2;
Class1* ptr = ThingFactory::createThing(thing);
std::cout << "ptr = " << ptr << std::endl
<< "<Class2>(ptr) = " << dynamic_cast<Class2*>(ptr) << std::endl
<< "<Class3>(ptr) = " << dynamic_cast<Class3*>(ptr) << std::endl
<< std::endl;
}
For me it outputs:
ptr = 0x214c010
<Class2>(ptr) = 0x214c010
<Class3>(ptr) = 0
You may also use boost::mpl, but it is not easy.
Upvotes: 2
Reputation: 119099
Unfortunately, C++ still doesn't have reflection. You can consider the tried-and-true X macro, if you have multiple places in your code where you'll have to employ the same mapping of values to types:
#define CASES \
X(Thing::Ex1, Class1) \
X(Thing::Ex2, Class2) \
X(Thing::Ex3, Class3) \
// ...
// ...
switch (_thing) {
#define X(v, T) \
case (v): \
ptr = new (T)(); \
break;
CASES
#undef X
}
// ... do other things with CASES and a different X macro
// ...
#undef CASES
Upvotes: 2