【问题标题】:Dynamic mapping of enum value (int) to type枚举值(int)到类型的动态映射
【发布时间】:2012-08-03 14:25:56
【问题描述】:

看来这个问题在我们的工作中很常见。

我们正在通过网络发送一个 int 或 enum 值,然后我们接收到我们想要创建/调用特定对象/函数的它。

最简单的解决方案是使用 switch 语句,如下所示:

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

它工作得很好,但是我们会有很多这样的开关块,当我们创建新的值和类型时,我们需要改变它们。看起来是对的。

其他可能性是使用模板。但我们不能,因为枚举的值是在运行时定义的。

是否有任何正确的设计模式或任何正确的方法?

这似乎是日常编码中非常普遍和常见的问题......

【问题讨论】:

  • 您是否有机会在 Microsoft 环境中工作?在 C# 中,我可以在枚举上定义自己的属性。从枚举到类型是相当简单的反射。
  • 完全没有。只有Linux。但是,如果你写更多关于这种方法的东西,它可能是值得和有趣的。
  • 别着急,我把机制的简要总结放在下面为你解答

标签: c++ enums mapping


【解决方案1】:

试试地图:

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()]();

(这当然很粗略;至少你会有错误检查,以及每个班级的自我注册方案,这样你就不会忘记注册一个班级。)

【讨论】:

    【解决方案2】:

    您实际上可以通过一些模板技巧来做到这一点:

    #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;
    };
    

    这允许您从给定的enum 创建一个新的派生对象,通过说

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

    但是,您必须有一些 EnumFactoryImpl 对象,这些对象在某些函数或命名空间中可能是静态的。

    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
    }
    

    这些行是您的源代码将enum 值映射到派生类型的单点。因此,您将所有内容都放在同一个位置,并且没有冗余(这消除了在添加新派生类型时忘记在一些地方更改它的问题)。

    【讨论】:

    • 当从网络检索枚举值时,它会在动态上下文中工作吗?
    • @kogut:你的意思是create 的参数值是动态的吗?一定会的! --- 你可以甚至在运行时建立新的关系(在枚举值和派生类型之间),但是newing EnumFactoryImpl 对象。但你可能不需要那个。
    【解决方案3】:

    一种选择是维护可以创建具体类型的创建者字典(具有相同的接口)。现在创建代码将在字典中搜索一个 int 值(来自客户端发送的枚举)并调用 create 方法,该方法通过基类指针返回具体对象。

    字典可以在一个地方初始化,具体的创建者对应每个可能的枚举值。

    这里的问题是当你添加一个新类型的对象时你必须扩展这个字典初始化代码。避免的方法如下。

    1. 让创建者寻找一个单例工厂实例,并在构造函数中注册自己,使用枚举类型(整数)创建具体对象。
    2. 为一个/一组创建者创建 DLL,并拥有创建者的全局实例。
    3. DLL 的名称可以输入到一个配置文件中,该文件由工厂在初始化时读取。工厂加载此文件中的所有 DLL,这会导致创建静态对象,这些对象将自己注册到工厂。
    4. 现在工厂拥有所有类型枚举的映射,它可以使用具体的对象创建者创建。
    5. 实施相同的对象创建者查找机制来创建对象。

    现在,工厂根本不需要扩展,因为第 3,4 和 5 步不会因为引入的新对象而改变。第 1 步可以在一个地方实施。

    您只需为每个应该存在的新具体类型添加一个全局对象,因为 C++ 本身不支持反射。

    【讨论】:

      【解决方案4】:

      kogut,我不建议将其作为答案,但由于您要求我扩展我对您最初问题的评论,这里是 .net 环境为您提供的非常简短的摘要......

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

      所以你有你的基本枚举一、二、三等,它的工作原理就像....er....一个枚举!

      但是您还编写了一个名为 MyAttribute 的类(事实上,要了解这方面的更多信息,只需搜索 Attributes 即可)。但正如您所见,这允许您在设计时说,某某枚举值与某某类相关联。

      此信息存储在枚举的元数据中(托管环境的值!),并且可以在运行时查询(使用反射)。不用说这非常强大,我已经使用这种机制系统地删除了您问题的其他答案中提出的大量地图。

      一个有用的例子是......在我使用的一个客户中,约定是将状态作为字符串存储在数据库中,因为它们对于需要运行表查询的人来说更具可读性.但这在应用程序中毫无意义,其中状态作为枚举推送。采用上述方法(使用字符串而不是类型),当数据被读取和写入时,这种转换发生在一行代码中。另外,当然,一旦你定义了 MyAttribute,它就可以被标记到你喜欢的任何枚举上。

      如果这些天我选择的语言是 c#,但这在(托管)c++ 中也很好。

      【讨论】:

      • 感谢您的回答。很高兴知道这些事情。
      猜你喜欢
      • 2019-04-09
      • 1970-01-01
      • 2018-08-01
      • 2020-08-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多