C++中的分层枚举

Joh*_*ice 13 c++ enums templates design-patterns cross-platform

我正在研究消息解析器/生成器子系统.我正在创建一个自动生成器,它使用包含有关此协议的所有信息的数据库,包括枚举列表,以生成代码.我遇到的一件事是需要分层枚举.

更新

(我试图通过不描述完整的问题来简化事情,但下面的评论显然我错误地简化了太多.)

正在使用的数据库将存储为简化字符串(客户决策),但协议仅说"字节三元组"(又名Hierarchical Enum).完整的问题可以这样描述:

给定一组唯一的字符串,其各自对应一个独特的三重的,1)找到任何给定的字符串三重,和 2)找到任何给定的三元组的字符串.确保考虑"Undefined"和"No Statement"枚举(没有与之关联的字符串).[正如一张海报所说,是的,这是疯了.]

(警告:我已经做了十多年的C++,但去年我一直在做Java - 我的C++可能已"损坏"了.)

因此,使用一个公认的人为例子,给出:

// There is only one category
// POP= "P", COUNTRY= "K", CLASSICAL= "C"
enum Category {POP, COUNTRY, CLASSICAL};

// There is one Type enum for each Category.
// ROCK= "R", BIG_BAND = "B", COUNTRY_POP= "C" 
enum PopType {ROCK, BIG_BAND, COUNTRY_POP};
enum CountryType {CLASSICAL_COUNTRY, MODERN_COUNTRY, BLUEGRASS, COUNTRY_AND_WESTERN};
// ...

// There is one Subtype for each Type
// EIGHTIES= "E", HEAVY_METAL= "H", SOFT_ROCK= "S"
enum RockSubType { EIGHTIES, HEAVY_METAL, SOFT_ROCK};
// ...
Run Code Online (Sandbox Code Playgroud)

当我得到0,0,0(Pop,Rock,Eighties)时,我需要将其转换为"PRE".相反,如果我在数据库中看到"PC",则需要将其作为0,2(Pop,Country,NULL)发送出去.

我公然忽略了"Undefined"和No Statement".从字符串生成三元组看起来很简单(使用无序映射,字符串为三元组).从三元组生成一个字符串(可能包含一个NULL)最后一个条目... ...没那么多.我所知道的大多数"枚举技巧"都不起作用:例如,类型重复值 - 每个类型枚举从零开始 - 所以我无法索引基于数组在Enum值上抓取字符串.

我得到的是这种关系.乍一看,这似乎是一个相当直接的"is-a"关系,但这不起作用,因为这种情况是双向的.leaf - > root导航非常简单,适用于类层次结构; 不幸的是,走另一条路并不是那么直截了当.

我不能"手动"这个 - 我必须生成代码 - 所以这可能会消除任何基于XML的解决方案.它也必须"相当快"."Java解决方案"涉及使用受保护的静态变量,在构造时初始化和抽象基类; 但是,我不相信这会在C++(初始化顺序等)中起作用.另外,在美学上,我觉得这应该是...更"常".我见过的解决此问题的其他代码使用了联合,明确列出了联合中的所有枚举类型.

我能想出的另一件事是使用模板专业化和明确的专业化,但我不知所措.我对此进行了网络搜索,但我发现没有什么可以告诉我它是否会起作用.但是,如果它可以通过联合完成,那么不能用模板专业化来完成吗?

是否可以使用模板,专业化,显式专业化来做这样的事情?是否有另一个更明显的解决方案(即我忘记的设计模式),我错过了?

哦,在我忘记之前 - 解决方案必须是便携式的.更具体地说,它必须适用于Windows(Visual Studio 2010)和Redhat Enterprise 6/Centos 6(GCC 4.4.4 IIRC).

而且,为免我遗忘,这个协议是巨大的.理论上最大值约为133,000个条目; 一旦我包含"Undefined"和"No Statement",我可能会有很多条目.

谢谢.

Mat*_* M. 9

实际上,你在这里有点紧张.

我的建议意味着首先使用3个枚举:

  • 类别
  • 类型
  • 子类型

在各种类型或子类型之间没有区别(起初)(我们只是将它们全部放在同一个篮子中).

然后,我只想使用一个结构:

struct MusicType {
  Category category;
  Type type;
  SubType subtype;
};
Run Code Online (Sandbox Code Playgroud)

并定义一个简单set的有效类型:

struct MusicTypeLess {
  bool operator()(MusicType const& left, MusicType const& right) const {
    if (left.category < right.category) { return true; }
    if (left.category > right.category) { return false; }

    if (left.type < right.type) { return true; }
    if (left.type > right.type) { return false; }

    return left.subtype < right.subtype;
  }
};

MusicType MusicTypes[] = {
  { Category::Pop, Type::Rock, SubType::EightiesRock },
  ...
};

// Sort it on initialization or define in sorted during generation
Run Code Online (Sandbox Code Playgroud)

然后您可以定义简单查询:

typedef std::pair<MusicType const*, MusicType const*> MusicTypeRange;

MusicTypeRange listAll() {
  return MusicTypeRange(MusicTypes, MusicTypes + size(MusicTypes));
}

namespace {
  struct MusicTypeCategorySearch {
    bool operator()(MusicType const& left, MusicType const& right) const {
      return left.category < right.category;
    }
  };
}

MusicTypeRange searchByCategory(Category cat) {
  MusicType const search = { cat, /* doesn't matter */ };
  return std::equal_range(MusicTypes,
                          MusicTypes + size(MusicTypes),
                          search,
                          MusicTypeCategorySearch());
}

namespace {
  struct MusicTypeTypeSearch {
    bool operator()(MusicType const& left, MusicType const& right) const {
      if (left.category < right.category) { return true; }
      if (left.category > right.category) { return false; }

      return left.type < right.type;
    }
  };
}

MusicTypeRange searchByType(Category cat, Type type) {
  MusicType const search = { cat, type, /* doesn't matter */ };
  return std::equal_range(MusicTypes,
                          MusicTypes + size(MusicTypes),
                          search,
                          MusicTypeTypeSearch ());
}

// little supplement :)
bool exists(MusicType const& mt) {
  return std::binary_search(MusicTypes, MusicTypes + size(MusicTypes), mt);
}
Run Code Online (Sandbox Code Playgroud)

因为数组是排序的,所以操作很快(log N),所以它应该顺利进行.


Joh*_*ice 0

首先,感谢大家的帮助。由于这个问题的性质,我实际上无法“按原样”使用任何答案:

  • 枚举重复它们的值(每个枚举可以与其兄弟具有相同的数值,但具有不同的标签和“含义”)
  • 与枚举关联的字符串也可以重复(给定的枚举可以与同级枚举具有相同的字符串,但具有不同的含义)。

我最终找到了Boost bimap,事实证明bimap层次结构可以很好地解决这个问题。对于那些还没有见过它们的人来说,Boost“bimap”是一个双向容器,它使用一对作为键,另一个作为值。

我可以制作一个bimap“整数,字符串”(在本例中为 uint8_t,因为这里的枚举都保证很小),并添加“子枚举”作为与使用相关的bimap信息with_info。

层次结构代码如下所示:

// Tags
struct category_enum_value {};
struct type_enum_value {};
struct subtype_enum_value {};
struct category_string {};
struct music_type_string {};
struct music_subtype_string {};
struct music_type_info {};
struct music_subtype_info {};

// Typedefs
typedef bimap<
    unordered_set_of< tagged<uint8_t, subtype_enum_value> >,
    unordered_set_of< tagged<std::string, music_subtype_string> >
> music_subtype;
typedef music_subtype::value_type music_subtype_value;

typedef bimap<
    unordered_set_of< tagged<uint8_t, type_enum_value> >,
    unordered_set_of< tagged<std::string, music_type_string> >,
    with_info< tagged<music_subtype, music_subtype_info> >
> music_type_type;
typedef music_type_type::value_type music_type_value;

typedef bimap<
    unordered_set_of< tagged<uint8_t, category_enum_value> >,
    unordered_set_of< tagged<std::string, category_string> >,
    with_info< tagged<music_type_type, music_type_info> > 
> category_type;
typedef category_type::value_type category_value;
Run Code Online (Sandbox Code Playgroud)

我选择unordered_set是出于性能原因。由于这严格来说是一个“常量”层次结构,因此我不必担心插入和删除时间。而且因为我永远不会比较顺序,所以我不必担心排序。

要通过枚举值获取类别信息(给定枚举时获取字符串值),我使用标签category_enum_value:

    category_type::map_by<category_enum_value>::iterator cat_it = categories.by<category_enum_value>().find(category);
if(cat_it != categories.by<category_enum_value>().end())
{
    const std::string &categoryString = cat_it->get_right();
            // ...
Run Code Online (Sandbox Code Playgroud)

通过使用type_enum_value标签(子类型几乎相同),我可以从中获取适当的类型信息:

    music_type_type &music_type_reference = cat_it->get<music_type_info>();
    music_type_type::map_by<type_enum_value>::iterator type_it = music_type_reference.by<type_enum_value>().find(type);
    if(type_it != music_type_reference.by<type_enum_value>().end())
    {
               // ... second verse, same as the first ...
Run Code Online (Sandbox Code Playgroud)

要获取给定字符串的枚举值,请将标记更改为category_string并使用与以前类似的方法:

    std::string charToFind = stringToFind.substr(0, 1);
    category_type::map_by<category_string>::iterator cat_it = categories.by<category_string>().find(charToFind);
    if(cat_it != categories.by<category_string>().end())
    {
        retval.first = cat_it->get_left();
                    // ... and the beat goes on ...
Run Code Online (Sandbox Code Playgroud)

任何给定级别所需的任何附加信息(例如菜单项字符串)都可以通过将信息类型从 a 更改bimap为struct包含 abimap以及我可能需要的任何信息来添加。

由于这些都是常量值,因此我可以“预先”完成所有艰苦的工作并设计简单的查找函数 - O(1) - 以获得我需要的东西。