Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Dynamic mapping of enum value (int) to type

Tags:

c++

enums

mapping

It appeared that this problem is quite common in our job.

We we are sending an int or enum value through the network, then we receive it we would like to create/call a particular object/function.

The most simply solution would be to use the switch statement, like below:

switch (value) {
    case FANCY_TYPE_VALUE: return new FancyType();
}

It works fine, but we would have plenty of these switch blocks, and when we create new value and type, we would need to change all of them. It does seem right.

Other possibility would be to use the templates. But we cannot, since the value of enum is defined in runtime.

Is there any right design pattern for that, or any right approach?

It seems like a very general and common problem in every day coding...

like image 728
kokosing Avatar asked Dec 11 '22 23:12

kokosing


2 Answers

Try a map:

struct Base { };
struct Der1 : Base { static Base * create() { return new Der1; } };
struct Der2 : Base { static Base * create() { return new Der2; } };
struct Der3 : Base { static Base * create() { return new Der3; } };

std::map<int, Base * (*)()> creators;

creators[12] = &Der1::create;
creators[29] = &Der2::create;
creators[85] = &Der3::create;

Base * p = creators[get_id_from_network()]();

(This is of course really crude; at the very least you'd have error checking, and a per-class self-registration scheme so you can't forget to register a class.)

like image 136
Kerrek SB Avatar answered Dec 14 '22 11:12

Kerrek SB


You can actually do this with some template trickery:

#include <map>

template <typename Enum, typename Base>
class EnumFactory {
  public:
    static Base* create(Enum e) {
      typename std::map<Enum,EnumFactory<Enum,Base>*>::const_iterator const it = lookup().find(e);
      if (it == lookup().end())
        return 0;
      return it->second->create();
    }
  protected:
    static std::map<Enum,EnumFactory<Enum,Base>*>& lookup() {
      static std::map<Enum,EnumFactory<Enum,Base>*> l;
      return l;
    }
  private:
    virtual Base* create() = 0;
};

template <typename Enum, typename Base, typename Der>
class EnumFactoryImpl : public EnumFactory<Enum,Base> {
  public:
    EnumFactoryImpl(Enum key)
      : position(this->lookup().insert(std::make_pair<Enum,EnumFactory<Enum,Base>*>(key,this)).first) {
    }
    ~EnumFactoryImpl() {
      this->lookup().erase(position);
    }
  private:
    virtual Base* create() {
      return new Der();
    }
    typename std::map<Enum,EnumFactory<Enum,Base>*>::iterator position;
};

This allows you to create a new derived object from a given enum, by saying

// will create a new `FancyType` object if `value` evaluates to `FANCY_TYPE_VALUE` at runtime
EnumFactory<MyEnum,MyBase>::create(value)

However, you have to have some EnumFactoryImpl objects, which could be static in some function or namespace.

namespace {
  EnumFactoryImpl<MyEnum,MyBase,Derived1> const fi1(ENUM_VALUE_1);
  EnumFactoryImpl<MyEnum,MyBase,Derived2> const fi2(ENUM_VALUE_2);
  EnumFactoryImpl<MyEnum,MyBase,Derived3> const fi3(ENUM_VALUE_3);
  EnumFactoryImpl<MyEnum,MyBase,FancyType> const fi1(FANCY_TYPE_VALUE); // your example
}

These lines are the single point where your source code maps enum values to derived types. So you have everything at the same location, and no redundancy (this eliminates the problem of forgetting to change it in some places, when adding new derived types).

like image 37
bitmask Avatar answered Dec 14 '22 13:12

bitmask