枚举值(int)到类型的动态映射

时间:2012-08-06 15:28:47

标签: c++ enums mapping

看来这个问题在我们的工作中非常普遍。

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

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

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

它工作正常,但我们会有很多这些开关块,当我们创建新的值和类型时,我们需要更改所有这些。这似乎是对的。

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

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

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

4 个答案:

答案 0 :(得分:5)

试试地图:

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

(这当然非常粗糙;至少你有错误检查和每班自我注册计划,所以你不能忘记注册一个班级。)

答案 1 :(得分:3)

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

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

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

  1. 让创建者查找单例工厂实例,并在构造函数中使用类型枚举(整数)注册自己,使用它可以创建一个混凝土对象。
  2. 为一个/一组创建者创建一个DLL,并拥有一个创建者的全局实例。
  3. 可以在配置文件中输入DLL的名称,该文件由工厂在初始化时读取。工厂加载此文件中的所有DLL,这导致创建静态对象,这些对象将自己注册到工厂。
  4. 现在,工厂拥有可以使用具体对象创建者创建的所有类型枚举的映射。
  5. 实现了相同的对象创建者查找机制来创建对象。
  6. 现在,工厂根本不需要扩展,因为步骤3,4和5不会因引入的新对象而改变。第1步可以在一个地方实施。

    您唯一需要做的就是为每个新的具体类型添加一个全局对象,因为C ++本身不支持反射。

答案 2 :(得分:3)

你可以用一些模板技巧来实现这一点:

#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值映射到派生类型的单一点。因此,您可以将所有内容放在同一位置,并且没有冗余(这可以消除在添加新的派生类型时忘记在某些位置更改它的问题)。

答案 3 :(得分:2)

kogut,我不建议这作为答案,但是既然你要求我扩展我对你原来问题的评论,这里是对.net环境给你的简要总结......

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

所以你有你的基本枚举一,二,三等,就像......呃......一个枚举!

但是你也编写了一个名为MyAttribute的类(事实上,在这个领域有更多信息,只需要搜索属性)。但正如您所看到的,这可以让您在设计时说出这样一个枚举值与这样一个类相关联。

此信息存储在枚举的元数据(托管环境的值!)中,可以在运行时查询(使用Reflection)。毋庸置疑,这是非常强大的,我已经使用这种机制系统地去除了你问题的其他答案中提出的那种地图。

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

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