简体   繁体   English

C ++中的分层枚举

[英]Hierarchical Enums in C++

I'm working on a message parser/generator subsystem. 我正在研究消息解析器/生成器子系统。 I'm creating an auto-generator that uses a database that contains all of the information about this protocol, including enum lists, to generate the code. 我正在创建一个自动生成器,它使用包含有关此协议的所有信息的数据库,包括枚举列表,以生成代码。 One thing I came across is the need for hierarchical enumerations. 我遇到的一件事是需要分层枚举。

updated 更新

(I was trying to simplify things by not describing the full problem, but the comments below make it obvious that I erred by simplifying too much.) (我试图通过不描述完整的问题来简化事情,但下面的评论显然我错误地简化了太多。)

The Database being used will store things as simplified strings (customer decision), but the protocol only speaks "byte triplets" (aka Hierarchical Enum). 正在使用的数据库将存储为简化字符串(客户决策),但协议仅说“字节三元组”(又名Hierarchical Enum)。 The full problem could be described as such: 完整的问题可以这样描述:

Given a set of unique strings that each correspond with a unique triplet, 1) find the triplet for any given string, and 2) find the string for any given triplet. 给定一组唯一的字符串,其各自对应一个独特的三重的,1)找到任何给定的字符串三重, 2)找到任何给定的三元组的字符串。 Make sure to account for "Undefined" and "No Statement" enumerations (which do not have strings associated with them). 确保考虑“Undefined”和“No Statement”枚举(没有与之关联的字符串)。 [As one poster noted, yes it is insane.] [正如一张海报所说,是的,它疯了。]

(Caveat: I've been doing C++ for well over a decade, but I've been doing Java this last year -- my C++ is probably "corrupted".) (警告:我已经做了十多年的C ++,但去年我一直在做Java - 我的C ++可能已“损坏”了。)

So, to use an admittedly contrived example, given: 因此,使用一个公认的人为例子,给出:

// 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};
// ...

When I get 0, 0, 0 (Pop, Rock, Eighties), I need to translate that to "PRE". 当我得到0,0,0(Pop,Rock,Eighties)时,我需要将其转换为“PRE”。 Conversely, if I see "PC" in the Database, that needs to be sent out the wire as 0, 2 (Pop, Country, NULL). 相反,如果我在数据库中看到“PC”,则需要将其作为0,2(Pop,Country,NULL)发送出去。

I'm blatantly ignoring "Undefined" and No Statement" at this point. Generating a triplet from a string seems straight forward (use an unordered map, string to triple). Generating a string from a triplet (that may contain a NULL in the last entry) ... not so much. Most of the "enum tricks" that I know won't work: for instance, Types repeat values -- each Type enum starts at zero -- so I can't index an array based on the Enum value to grab the string. 我公然忽略了“Undefined”和No Statement“。从字符串生成三元组看起来很简单(使用无序映射,字符串为三元组)。从三元组生成一个字符串(可能包含一个NULL)最后一个条目... ...没那么多。我所知道的大多数“枚举技巧”都不起作用:例如,类型重复值 - 每个类型枚举从零开始 - 所以我不能索引基于数组在Enum值上抓取字符串。

What's got me is the relationship. 我得到的是这种关系。 At first glance it appears to be a fairly straight forward "is-a" relationship, but that doesn't work because this case is bidirectional. 乍一看,这似乎是一个相当直接的“is-a”关系,但这不起作用,因为这种情况是双向的。 The leaf -> root navigation is very straight forward, and would be appropriate for a class hierarchy; leaf - > root导航非常简单,适用于类层次结构; unfortunately, going the other way is not so straight forward. 不幸的是,走另一条路并不是那么直截了当。

I cannot "hand roll" this -- I have to generate the code -- so that probably eliminates any XML based solutions. 我不能“手动”这个 - 我必须生成代码 - 所以这可能会消除任何基于XML的解决方案。 It also has to be "reasonably fast". 它也必须“相当快”。 The "Java Solution" involves using protected static variables, initialized on construction, and abstract base classes; “Java解决方案”涉及使用受保护的静态变量,在构造时初始化和抽象基类; however, I do not believe this would work in C++ (order of initialization, etc.). 但是,我不相信这会在C ++(初始化顺序等)中起作用。 Plus, aesthetically, I feel this should be ... more "const". 另外,从美学角度来说,我觉得这应该是...更多“常量”。 Other code I've seen that tackles this problem uses unions, explicitly listing all of the enum types in the union. 我见过的解决此问题的其他代码使用了联合,明确列出了联合中的所有枚举类型。

The only other thing I can come up with is using Template Specialization and explicit specialization, but I'm at a loss. 我能想出的另一件事是使用模板专业化和明确的专业化,但我不知所措。 I did a web search on this, but I found nothing that would tell me if it would even work. 我对此进行了网络搜索,但我发现没有什么可以告诉我它是否会起作用。 Still, if it can be done with a union, can't it be done with Template Specialization? 但是,如果它可以通过联合完成,那么不能用模板专业化来完成吗?

Is it possible to do something like this using templates, specialization, explicit specialization? 是否可以使用模板,专业化,显式专业化来做这样的事情? Is there another, more obvious, solution (ie a design pattern that I've forgotten) that I'm missing? 是否有另一个更明显的解决方案(即我忘记的设计模式),我错过了?

Oh, before I forget -- the solution must be portable. 哦,在我忘记之前 - 解决方案必须是便携式的。 More specifically, it must work on Windows (Visual Studio 2010) and Redhat Enterprise 6/Centos 6 (GCC 4.4.4 IIRC). 更具体地说,它必须适用于Windows(Visual Studio 2010)和Redhat Enterprise 6 / Centos 6(GCC 4.4.4 IIRC)。

And, lest I forget, this protocol is huge . 而且,为免我遗忘,这个协议是巨大的 The theoretical max on this is about 133,000 entries; 理论上最大值约为133,000个条目; once I include "Undefined" and "No Statement" I'll probably have that many entries. 一旦我包含“Undefined”和“No Statement”,我可能会有很多条目。

Thanks. 谢谢。

Effectively, you are in a bit of a pinch here. 实际上,你在这里有点紧张。

My proposal would imply first using 3 enums: 我的建议意味着首先使用3个枚举:

  • Category 类别
  • Type 类型
  • SubType 子类型

With no distinction (at first) between the various types or subtypes (we just throw them all in the same basket). 在各种类型或子类型之间没有区别(起初)(我们只是将它们全部放在同一个篮子中)。

Then, I would simply use a structure: 然后,我只想使用一个结构:

struct MusicType {
  Category category;
  Type type;
  SubType subtype;
};

And define a simple set of valid types: 并定义一个简单的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

Then you can define simple queries: 然后您可以定义简单查询:

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

Because the array is sorted, the operations are fast (log N), so it should go smoothly. 因为数组是排序的,所以操作很快(log N),所以它应该顺利进行。

我认为Music类应该包含子类型...(has-a)也称为聚合。

The leaf -> root navigation is very straight forward, and would be appropriate for a class hierarchy; leaf - > root导航非常简单,适用于类层次结构; unfortunately, going the other way is not so straight forward. 不幸的是,走另一条路并不是那么直截了当。

I'm not really sure what value you're getting by using enums in the first place. 我不确定你首先使用枚举获得了什么价值。 Are there compelling reasons not just invent a Category class, and then connect together instances of them to model what you're trying to achieve? 是否有令人信服的理由不仅仅发明了一个Category类,然后将它们的实例连接在一起来模拟你想要实现的目标? (I'm reminded of the Qt State Machine Framework ...) (我想起了Qt状态机框架 ......)

In my mind, the good thing about it is how simple it is, and easy to adapt as your needs change. 在我看来,它的好处在于它是多么简单,并且随着您的需求变化而易于适应。 It's boring code. 这是无聊的代码。 You're not really pushing the compile-time features of the language much. 你并没有真正推动语言的编译时功能。 But you say this is generated code, so don't really have to worry about someone introducing bugs with a cyclic category heirarchy. 但是你说这是生成的代码,所以不必担心有人会引入带有循环类heirarchy的bug。 Just make sure such things aren't generated. 只要确保没有生成这样的东西。

UPDATE Okay I read your scenario updates and it really sounds like you're looking at a database task here. 更新好的我读了你的场景更新,听起来你真的在看这里的数据库任务。 The word "enum" doesn't even come to mind for this. “enum”这个词甚至没有想到这一点。 Have you considered SQLite? 你考虑过SQLite吗?

http://en.wikipedia.org/wiki/SQLite http://en.wikipedia.org/wiki/SQLite

Still, putting aside the question of where you're getting this insane list of 133,000 music genres, I have modified my code to give you a concrete performance metric for how C++ can handle runtime objects of that scale. 尽管如此,抛开你在这个疯狂的133,000个音乐类型列表的位置的问题,我修改了我的代码,为你提供了一个具体的性能指标,用于说明C ++如何处理该规模的运行时对象。 You'll max things out eventually, but on most machines it can still be fairly snappy...try it: 你最终会最大限度地解决问题,但在大多数机器上它仍然可以相当活泼...尝试一下:

#include <iostream>
#include <sstream>
#include <string>
#include <vector>
#include <set>
#include <algorithm>
#include <cstdlib>
using namespace std;

class Category {
private:
    string name;
    Category* parent;
    set<Category*> children;
private:
    static set<Category*> allCategories;
    static vector<Category*>* allCategoriesVector;
public:
    Category (string name, Category* parent) :
        name (name), parent (NULL)
    {
        resetParent(parent);
    }
    void resetParent(Category* newParent) {
        if (parent) {
            parent->children.erase(this);
            if (newParent == NULL) {
                allCategories.erase(this);
                if (allCategoriesVector != NULL) {
                    delete allCategoriesVector;
                    allCategoriesVector = NULL;
                }
            }
        } else {
            if (newParent != NULL) {
                allCategories.insert(this);
                if (allCategoriesVector != NULL) {
                    allCategoriesVector->push_back(this);
                }
            }
        }
        set<Category*>::iterator i = children.begin();
        while (i != children.end()) {
            (*i)->parent = NULL;
            i++;
        } 

        if (newParent) {
            newParent->children.insert(this);
        }

        parent = newParent;
    }
    Category* getRoot() {
       Category* result = this;
       while (result->parent != NULL) {
           result = result->parent;
       }
       return result;
    }
    const string& getNamePart() const {
        return name;
    }
    string getNamePath() const {
        if (parent) {
            return parent->getNamePath() + ":" + getNamePart();
        } else {
            return getNamePart();
        }
    }
    static const vector<Category*>& getAllCategoriesVector() {
        if (allCategoriesVector == NULL) {
           allCategoriesVector = new vector<Category*> (
               allCategories.begin(), allCategories.end()
           );
        }
        return *allCategoriesVector;
    }
    static Category* randomCategory() {
        if (allCategories.empty())
            return NULL;

        // kids: don't try this at home if you want a uniform distribution
        // http://stackoverflow.com/questions/5008804/generating-random-integer-from-a-range
        return getAllCategoriesVector()[rand() % allCategories.size()];
    }
    virtual ~Category() {
        resetParent(NULL);
    }
};
set<Category*> Category::allCategories;
vector<Category*>* Category::allCategoriesVector = NULL;

class CategoryManager {
public:
    Category Root;
        Category Pop;
            Category Rock;
                Category EightiesRock;
                Category HeavyMetal;
                Category SoftRock;
            Category CountryPop;
            Category BigBand;
        Category Country;
        Category Classical;
        Category Jazz;

private:
    set<Category*> moreCategories;
public:
    CategoryManager (int numRandomCategories = 0) :
        Root ("Category", NULL),
            Pop ("Pop", &Root),
                Rock ("Rock", &Pop),
                    EightiesRock ("EightiesRock", &Rock),
                    HeavyMetal ("HeavyMetal", &Rock),
                    SoftRock ("SoftRock", &Rock),
                CountryPop ("CountryPop", &Pop),
                BigBand ("BigBand", &Pop),
            Country ("Country", &Root),
            Classical ("Classical", &Root),
            Jazz ("Jazz", &Root)
    {
        // claim is that there are "hundreds" of these
        // lets make a bunch of them starting with no parent
        for (int i = 0; i < numRandomCategories; i++) {
            stringstream nameStream;
            nameStream << "RandomCategory" << i;
            moreCategories.insert(new Category(nameStream.str(), NULL));
        }

        // now that we have all the categories created, let's
        // reset their parents to something chosen randomly but
        // keep looking until we find one whose path goes up to Root
        set<Category*>::iterator i (moreCategories.begin());
        while (i != moreCategories.end()) {
            (*i)->resetParent(Category::randomCategory());
            i++;
        }
    }
    virtual ~CategoryManager () {
        set<Category*>::iterator i = moreCategories.begin();
        while (i != moreCategories.end()) {
            delete *i;
            i++;
        }
    }
};

int main() {
    CategoryManager cm (133000);

    // how to get to a named category
    cout << cm.EightiesRock.getNamePath() << "\n" << "\n";

    // pick some random categories to output
    for (int i = 0; i < 5; i++) {
        cout << Category::randomCategory()->getNamePath() << "\n";
    }

    return 0;
}

On my machine this rather promptly spat out: 在我的机器上,这很快就吐了出来:

Category:Pop:Rock:EightiesRock

Category:Pop:Rock:HeavyMetal:RandomCategory0:RandomCategory6:RandomCategory12:RandomCategory95:RandomCategory116:RandomCategory320:RandomCategory358:RandomCategory1728:RandomCategory6206:RandomCategory126075
Category:Country:RandomCategory80:RandomCategory766:RandomCategory2174
Category:Country:RandomCategory22:RandomCategory45:RandomCategory52:RandomCategory83:RandomCategory430:RandomCategory790:RandomCategory860:RandomCategory1628:RandomCategory1774:RandomCategory4136:RandomCategory10710:RandomCategory13124:RandomCategory19856:RandomCategory20810:RandomCategory43133
Category:Pop:Rock:HeavyMetal:RandomCategory0:RandomCategory5:RandomCategory138:RandomCategory142:RandomCategory752:RandomCategory2914:RandomCategory9516:RandomCategory13211:RandomCategory97800
Category:Pop:CountryPop:RandomCategory25:RandomCategory63:RandomCategory89:RandomCategory2895:RandomCategory3842:RandomCategory5735:RandomCategory48119:RandomCategory76663

I'll still say a database is the answer you're looking for here, but at the same time you'd be surprised how much abuse a compiler will take these days. 我仍然会说数据库是你在这里寻找的答案,但同时你会惊讶于编译器这些天会滥用多少。 133K file with each line being an object declaration is more tractable than it sounds. 每行作为对象声明的133K文件比听起来更容易处理。

Your lookups are runtime, so I don't really think a lot of static typing will help you much. 你的查找是运行时,所以我真的不认为很多静态类型会对你有所帮助。 I believe you could write them on top of the below if you really wanted them as well. 如果你真的想要它们,我相信你可以把它们写在下面。

I don't assume that programmers will be directly specifying these in their day to day coding. 我不认为程序员会在日常编码中直接指定这些。 They will be taking in runtime generated values and transforming it? 他们将采用运行时生成的值并对其进行转换?

Given that assumption, I would denormalize the enum. 鉴于这种假设,我会对枚举进行非规范化。 This may have some trade offs for getting warnings about when a switch statement is missing one of the values. 这可能需要一些权衡,以获得有关何时switch语句缺少其中一个值的警告。

struct MusicType {
  enum EnumValue {
    ROOT = 0
    ,Pop
    ,Pop_Rock
    ,Pop_Rock_EightiesRock
    ,Pop_Rock_HeavyMetal
    ,Pop_Rock_SoftRock
    ,Pop_CountryPop
    ,Pop_BigBand
    ,Country
    ,Classical
    ,Jazz
  };
  std::string getLeafString(EnumValue ev) {
    case (ev) {
      case Pop:         return "Pop";
      case Pop_Rock:    return "Rock";
      // ...
      default:
        throw std::runtime_error("Invalid MusicType (getLeafString)");
    }
  }
  // you could write code to do this easily without generating it too
  std::string getFullString(EnumValue ev) {
    case (ev) {
      case Pop:         return "Pop";
      case Pop_Rock:    return "Pop::Rock";
      // ...
      default:
        throw std::runtime_error("Invalid MusicType (getFullString)");
    }
  }

};

So then you need to map your relationships. 那么你需要映射你的关系。 It sounds like the number of levels is firm, but when that sort of assumption breaks it's really expensive to fix. 听起来水平的数量是坚定的,但是当这种假设破裂时,修复起来确实很昂贵。

There are a few ways to go about this. 有几种方法可以解决这个问题。 I think a data structure is the most straight forward to implement, though you could do a big huge switch. 我认为数据结构是最直接实现的,尽管你可以做一个巨大的转换。 I think that would be more trouble for similar performance. 我认为对于类似的表现来说会更麻烦。 Really, a switch statement is just a map in the code segment though, pick your poison. 实际上,switch语句只是代码段中的一个映射,但是选择你的毒药。

I like to solve problems like this that only resolve one level at a time. 我喜欢解决这样的问题,一次只解决一个级别。 This lets you have any number of levels. 这使您可以拥有任意数量的级别。 It makes this lowest level of abstraction simpler. 它使这个最低级别的抽象更简单。 It does make you write more "middleware," but that should be simpler to implement. 它确实会让你写出更多“中间件”,但这应该更容易实现。

void getChildren(MusicType::EnumValue ev, std::vector<MusicType::EnumValue> &children) {
  typedef std::multimap<MusicType::EnumValue, MusicType::EnumValue> relationships_t;
  typedef std::pair<MusicType::EnumValue, MusicType::EnumValue> mpair_t;
  static relationships_t relationships;
  static bool loaded = false;
  if (!loaded) {
    relationships.insert(mpair_t(MusicType::Pop, MusicType::Pop_Rock));
    relationships.insert(mpair_t(MusicType::Pop_Rock, MusicType::Pop_Rock_EightiesRock));
    // ..
  }
  // returning these iterators as a pair might be a more general interface
  relationships::iterator cur = relationships.lower_bound(ev);
  relationships::iterator end = relationships.upper_bound(ev);
  for (; cur != end; cur++) {
    children.push_back(cur->second);
  }
} 

MusicType::EnumValue getParent(MusicType::EnumValue ev) {
  case (ev) {
    case Pop:         return MusicType::ROOT;
    case Pop_Rock:    return MusicType::Pop;
    // ...
    default:
      throw std::runtime_error("Invalid MusicType (getParent)");
    }
}

The great part about separating it like this is that you can write any sort of combinatorial helpers you want for these without having to worry about structure too much. 像这样分离它的重要部分是你可以编写任何你想要的组合助手,而不必过多担心结构。

For GUI feedback, this should be fast enough. 对于GUI反馈,这应该足够快。 If you needed it faster, then you may be able to do some inversion of control to avoid a few copies. 如果你需要它更快,那么你可以做一些控制反转,以避免一些副本。 I don't think I'd start there though. 我不认为我会从那里开始。

You can add extra functionality without changing too much internally, which is my main concern with generated code usually. 您可以添加额外的功能而不会在内部进行太多更改,这通常是我对生成代码的主要关注点。 The open/closed principle is extremely important with generated code. 开放/封闭原则对于生成的代码非常重要。

I'm having trouble understanding your intent, but here's a random shot in the dark. 我无法理解你的意图,但这是在黑暗中随机拍摄的。 MusicCategory is a class that holds the value in Enum value . MusicCategory是一个保存Enum value PopTypes inherits publicly from MusicCategory , and so does RockTypes from PopTypes . PopTypes从公开继承MusicCategory ,也是如此RockTypesPopTypes As long as the program only stores/passes MusicCategory types, you can assign all types to it, from any of the derived class types. 只要程序只存储/传递MusicCategory类型,您就可以从任何派生类类型中为其分配所有类型。 Thus you can have MusicCategory Cat = RockTypes::SoftRock; 因此,你可以拥有MusicCategory Cat = RockTypes::SoftRock; , and if the enums are defined carefully, it would even set Pop / Rock appropriately. 如果仔细定义枚举,它甚至可以适当地设置Pop / Rock

struct MusicCategory{
   enum Enum {
              NoCategory = 0 | (0<<12),  //"0 |" isn't needed, but shows pattern
              Pop        = 0 | (1<<12), 
              Country    = 0 | (2<<12), 
              Classical  = 0 | (3<<12), 
              Jazz       = 0 | (4<<12),
              All        = INT_MAX} value; 
  //"ALL" forces enum to be big enough for subtypes
   MusicCategory(Enum e) :value(e) {} //this makes the magic work
   operator Enum&() {return value;}
   operator const Enum&() const {return value;}
   operator const int() const {return value;}
   const std::string & getString(MusicCategory::Enum category);
};

// Begin types
// This one is a subtype of MusicCategory::Pop
struct PopTypes : public MusicCategory {
   enum Enum { 
       NoType     = MusicCategory::Pop | (0<<6), 
       Rock       = MusicCategory::Pop | (1<<6), 
       CountryPop = MusicCategory::Pop | (2<<6), 
       BigBand    = MusicCategory::Pop | (3<<6),
       All        = INT_MAX};
   const std::string & getString(PopTypes::Enum category);
};
// ...

// Begin subtypes
struct RockTypes : public PopType {
   enum Enum { 
       NoSubType    = PopTypes::Rock | (0<<0),  //"<<0)" isn't needed, but shows pattern
       EightiesRock = PopTypes::Rock | (1<<0),
       HeavyMetal   = PopTypes::Rock | (2<<0), 
       SoftRock     = PopTypes::Rock | (3<<0),
       All          = INT_MAX};
   const std::string & getString(RockTypes::Enum category);
};

int main() {
    MusicCategory Cat; 
    // convertable to and from an int
    Cat = RockTypes::HeavyMetal;
    //automatically sets MusicCategory::Pop and PopTypes::Rock
    bool is_pop = (Cat & MusicCategory::Pop == MusicCategory::Pop);
    //returns true
    std:string str = MusicCategory::getString(Cat);
    //returns Pop
    str = PopTypes::getString(Cat);
    //returns Rock
    str = RockTypes::getString(Cat);
    //returns HeavyMetal
}

First, thanks to everyone for their help. 首先,感谢大家的帮助。 I wasn't actually able to use any of the answers "as is" because of the nature of this problem: 由于这个问题的性质,我实际上无法“按原样”使用任何答案:

  • enums repeat their values (every enum can have the same numerical values as it's siblings, but with a different label and "meaning") 枚举重复它们的值(每个枚举可以具有与其兄弟姐妹相同的数值,但具有不同的标签和“含义”)
  • strings associated with the enum can be repeated as well (a given enum can have the same string as a sibling, but with a different meaning). 与枚举相关联的字符串也可以重复(给定的枚举可以具有与兄弟相同的字符串,但具有不同的含义)。

I eventually found Boost bimaps and it turns out that a bimap hierarchy works well for this problem. 我最终找到了Boost bimaps ,事实证明bimap层次结构适用于这个问题。 For those that haven't seen them, Boost `bimap' is a bidirectional container that uses either of the pair as key and the other as value. 对于那些没有看过它们的人来说,Boost`bimap'是一个双向容器,它使用该对作为键,另一个作为值。

I can make a bimap of "integer, string" (uint8_t in this case, since the enums here are all guaranteed to be small) and add the, errr, "sub-enum", as information associated with the bimap using with_info . 我可以做一个“整数,字符串”的bimap (在这种情况下为uint8_t,因为这里的枚举都保证很小)并使用with_info添加errr,“sub-enum”作为与bimap相关的信息。

The hierarchy code looks something like this: 层次结构代码如下所示:

// 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;

I chose unordered_set for performance reasons. 出于性能原因,我选择了unordered_set Since this is strictly a "constant" hierarchy, I don't have to worry about insertion and deletion times. 由于这严格来说是一个“常量”层次结构,因此我不必担心插入和删除时间。 And because I'll never be comparing order, I don't have to worry about sorting. 因为我永远不会比较订单,所以我不必担心排序。

To get the Category information by enum value (get string values when given the enum), I use the category_enum_value tag: 要通过枚举值获取类别信息(在给定枚举时获取字符串值),我使用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();
            // ...

I get the appropriate Type information from this by doing this, using the type_enum_value tag (subtype is nearly identical): 我通过使用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 ...

To get the enum values given the string, change the tag to category_string and use similar methods as before: 要获取给定字符串的枚举值,请将标记更改为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 ...

Any additional information that I need for any given level (say, menu item strings) can be added by changing the info type from a bimap to a struct containing a bimap and whatever information I might need. 可以通过将信息类型从bimap更改为包含bimapstruct以及我可能需要的任何信息来添加任何给定级别(例如,菜单项字符串)所需的任何其他信息。

Since this is all constant values, I can do all the hard work "up front" and design simple look-up functions -- O(1) -- to get what I need. 由于这是所有常数值,我可以“预先”完成所有艰苦工作并设计简单的查找功能 - O(1) - 以获得我需要的东西。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM