Bluejay
Bluejay

Reputation: 351

Enums and initialising classes

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

Answers (3)

QuestionC
QuestionC

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

myaut
myaut

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

Brian Bi
Brian Bi

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

Related Questions