Dynamic mapping of enum value (int) to type

前端 未结 4 1183
清酒与你
清酒与你 2020-12-18 09:27

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

相关标签:
4条回答
  • 2020-12-18 09:44

    One option is to maintain a dictionary of creators(which has the same interface) that can create a concrete type. Now the creation code will search in the dictionary for an int value (resulting from the enum sent from the client) and call the create method, which returns the concrete object via a base-class pointer.

    The dictionary can be initialized at one place with the concrete creators corresponding to each possible enum values.

    The problem here is that you have to extend this dictionary initialization code when you add a new type of object. A way to avoid is as following.

    1. Let the creator look for a singleton factory instance and register itself in the constructor with the type enums(integers) with which it can create a concret object.
    2. Create a DLL for one/set of creators and have a global instance of the creators.
    3. The name of the DLL can be entered in a config file which is read by the factory in the initialization. The factory loads all the DLLs in this file and this results in the creation of the static objects which registers themselves with the factory.
    4. Now the factory has the map of all the type enums which it can create with the concrete object creators.
    5. The same object creator look-up mechanism is implemented to create the objects.

    Now, the factory doesn't need to be extended at all since step 3,4 and 5 doesn't change for new objects introduced. Step 1 can be implemented in one place.

    Only thing you need to do is to add a global object for each of the new concrete type which should be there since the C++ doesn't support reflection natively.

    0 讨论(0)
  • 2020-12-18 09:46

    kogut, I don't propose this as an answer, but since you ask me to expand on my comment on your original question here's a very brief summary of what the .net environment gives you...

    public enum MyEnum
    {
        [MyAttribute(typeof(ClassNone))]
        None,
        [MyAttribute(typeof(ClassOne))]
        One,
        [MyAttribute(typeof(ClassTwo))]
        Two,
        [MyAttribute(typeof(ClassThree))]
        Three
    }
    

    So you have your basic enum One, Two, Three etc. which works just like....er....an enum!

    But you also code up a class called MyAttribute (and in fact for more information in this area, just search for Attributes). But as you can see this allows you to say, at design time, that such-and-such an enum value is associated with such-and-such a class.

    This information is stored in the enum's metadata (the value of a managed environment!) and can be interrogated at runtime (using Reflection). Needless to say this is very powerful, I've used this mechanism to systematically strip out loads of maps of the kind proposed in other answers to your question.

    An example of the usefulness is this...at one client I worked with, the convention was to store statuses as strings in a database on the grounds that they would be more readable to a human who needed to run a table query. But this made no sense in the applications, where statuses were pushed through as enums. Take the above approach (with a string rather than a type) and this transform happened on a single line of code as data was read and written. Plus, of course, once you've defined MyAttribute it can be tagged onto any enum you like.

    My language if choice these days is c# but this would also be good in (managed) c++.

    0 讨论(0)
  • 2020-12-18 10:00

    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.)

    0 讨论(0)
  • 2020-12-18 10:05

    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).

    0 讨论(0)
提交回复
热议问题