代码之家  ›  专栏  ›  技术社区  ›  Andreas Bonini

C/C++:获得反射式枚举的方法有哪些?

  •  16
  • Andreas Bonini  · 技术社区  · 15 年前

    我遇到这种情况太多次了…

     enum Fruit {
      Apple,
      Banana,
      Pear,
      Tomato
     };
    

    现在我有 Fruit f; // banana 我想从 f "Banana" 或者我有 string s = "Banana" 从那以后我想去 Banana // enum value or int .

    到目前为止,我一直在做……假设枚举是在fruit.h中:

    // Fruit.cpp
    const char *Fruits[] = {
     "Apple",
     "Banana",
     "Pear",
     "Tomato",
     NULL
    };
    

    显然,这是一个混乱的解决方案。如果开发者在头部添加了一个新的水果,并且没有在水果中添加新的条目[(不能怪他,他们必须在两个不同的文件中!)应用程序开始蓬勃发展。

    有没有一个简单的方法来做我想做的事情,所有的事情都在一个文件中?预处理器黑客,外星魔法,任何东西……

    PS:这与反射“为一切”相反,在编译器中实现是非常简单的。看到这个问题有多普遍(至少对我来说),我真的不敢相信没有 reflective enum Fruit …即使在C++0X中也不行。

    PS2:我使用C++,但是我也把这个问题标记为C,因为C有同样的问题。如果你的解决方案只包括C++,我就可以了。

    10 回复  |  直到 8 年前
        1
  •  32
  •   Gonzalo    15 年前

    这需要在外部文件中定义水果。 这将是 水果 :

    #define FRUIT(name) name
    enum Fruit {
    #include "fruit-defs.h"
    NUM_FRUITS
    };
    #undef FRUIT
    #define FRUIT(name) #name
    const char *Fruits [] = {
    #include "fruit-defs.h"
    NULL
    };
    #undef FRUIT
    

    这就是 水果水果 :

    FRUIT(Banana),
    FRUIT(Apple),
    FRUIT(Pear),
    FRUIT(Tomato),
    

    只要值从0开始并且是连续的…

    更新: 如果需要非连续值,请使用C99将此解决方案与Richard Pennington的解决方案混合。比如说:

    // This would be in fruit-defs.h
    FRUIT(Banana, 7)
    ...
    // This one for the enum
    #define FRUIT(name, number) name = number
    ....
    // This one for the char *[]
    #define FRUIT(name, number) [number] = #name
    
        2
  •  9
  •   Richard Pennington    15 年前

    我发现的C99方法有助于减少错误:

    enum Fruit {
      APPLE,
      BANANA
    };
    const char* Fruits[] = {
     [APPLE] = "APPLE",
     [BANANA] = "BANANA"
    };
    

    您可以添加枚举,即使在中间,也不能破坏旧的定义。当然,对于您忘记的值,您仍然可以获得空字符串。

        3
  •  4
  •   R Samuel Klatchko    15 年前

    我过去所做的一个技巧是添加一个额外的枚举,然后执行编译时断言(例如 Boost's )要确保两者保持同步:

    enum Fruit {
        APPLE,
        BANANA,
    
        // MUST BE LAST ENUM
        LAST_FRUIT
    };
    
    const char *FruitNames[] =
    {
        "Apple",
        "Banana",
    };
    
    BOOST_STATIC_ASSERT((sizeof(FruitNames) / sizeof(*FruitNames)) == LAST_FRUIT);
    

    这至少可以防止有人忘记添加到枚举和名称数组中,并在尝试编译时立即通知他们。

        4
  •  4
  •   Hyman Rosen    15 年前

    关于宏解决方案的一个注释-枚举器不需要单独的文件。只需使用另一个宏:

    #define FRUITs \ 
        FRUIT(Banana), \ 
        FRUIT(Apple), \ 
        FRUIT(Pear), \ 
        FRUIT(Tomato)
    

    (不过,我可能会去掉逗号,并根据需要将它们合并到水果宏中。)

        5
  •  3
  •   David Grayson    15 年前

    如果你做了这样的事怎么办?

    enum Fruit {
      Apple,
      Banana,
      NumFruits
    };
    
    const char *Fruits[NumFruits] = {
     "Apple",
     "Banana",
    };
    

    然后,如果向Fruit枚举添加一个新条目,编译器应该会抱怨数组的初始值设定项中没有足够的条目,因此您将被迫向数组添加一个条目。

    因此,它可以防止数组大小错误,但不能帮助您确保字符串是正确的。

        6
  •  1
  •   Eld    15 年前

    可以为它创建类结构:

    class Fruit { 
       int value; char const * name ; 
       protected:
       Fruit( int v, char const * n ) : value(v), name(n) {}
       public:
       int asInt() const { return value ; }
       char const * cstr() { return name ; } 
    } ;
    #define MAKE_FRUIT_ELEMENT( x, v ) class x : public Fruit { x() : Fruit( v, #x ) {} }
    
    // Then somewhere:
    MAKE_FRUIT_ELEMENT(Apple, 1);
    MAKE_FRUIT_ELEMENT(Banana, 2);
    MAKE_FRUIT_ELEMENT(Pear, 3);
    

    然后你可以有一个需要水果的功能,它甚至更安全。

    void foo( Fruit f ) {
      std::cout << f.cstr() << std::endl;
      switch (f.asInt()) { /* do whatever * } ;
    }
    

    它的大小比一个枚举大2倍。但很可能这并不重要。

        7
  •  1
  •   user181548    15 年前

    正如其他回答这个问题的人所展示的那样,单独使用C预处理器并没有一种真正干净的方法(“D.R.Y”)。问题是,您需要定义一个枚举大小的数组,该数组包含与每个枚举值对应的字符串,而C预处理器不够智能,无法做到这一点。我要做的是创建这样的文本文件:

    %status ok
    %meaning
    The routine completed its work successfully.
    %
    
    %status eof_reading_content
    %meaning
    
    The routine encountered the end of the input before it expected
    to. 
    
    %
    

    这里是%的标记分隔符。

    然后是一个Perl脚本,其工作部分如下,

    sub get_statuses
    {
        my ($base_name, $prefix) = @_;
        my @statuses;
        my $status_txt_file = "$base_name.txt";
        my $status_text = file_slurp ($status_txt_file);
        while ($status_text =~ 
           m/
            \%status\s+([a-zA-Z_][a-zA-Z0-9_]*)\s*\n
            \%meaning\s*(.*?)\s*\n\%\s*\n
            /gxs) {
        my ($code, $meaning) = ($1, $2);
        $code = $prefix."_$code";
        $meaning =~ s/\s+/ /g;
        push @statuses, [$code, $meaning];
        }
        return @statuses;
    }
    

    读取此文件并写入头文件:

    typedef enum kinopiko_status {
        kinopiko_status_ok,
        kinopiko_status_eof_reading_content,
    

    C文件:

    /* Generated by ./kinopiko-status.pl at 2009-11-09 23:45. */
    #include "kinopiko-status.h"
    const char * kinopiko_status_strings[26] = {
    "The routine completed its work successfully.",
    "The routine encountered the end of the input before it expected to. ",
    

    使用顶部的输入文件。它还通过计算输入行来计算这里的数字26。(事实上有26种可能的状态。)

    然后使用 make .

        8
  •  1
  •   Matthieu M.    15 年前

    总的来说,我不喜欢宏观解决方案,尽管我承认在那里很难避免它们。

    我个人选择了一个自定义类来包装我的枚举。目标是提供比传统的枚举(比如迭代)更多的东西。

    在封面下面,我用 std::map 将枚举映射到其 std::string 对应的。然后我可以使用它来迭代枚举和“漂亮打印”我的枚举,或者从文件中读取的字符串初始化它。

    当然,问题是定义,因为我必须首先声明枚举,然后映射它…但这就是你使用它们的代价。

    另外,我使用的不是真正的枚举,而是指向映射(在封面下)的常量迭代器来表示枚举值(使用 end 表示无效值)。

        9
  •  1
  •   Herman Narkaytis    9 年前

    看看MetaResc库 https://github.com/alexanderchuranov/Metaresc

    它为类型声明提供接口,该声明还将为该类型生成元数据。基于元数据,您可以轻松地序列化/反序列化任何复杂的对象。开箱即用,您可以序列化/反序列化XML、JSON、XDR、类似Lisp的表示法、C-init表示法。

    下面是一个简单的例子:

    #include <stdio.h>
    #include <stdlib.h>
    #include <inttypes.h>
    
    #include "metaresc.h"
    
    TYPEDEF_ENUM (fruit_t,
                  Apple,
                  Banana,
                  Pear,
                  Tomato,
                  );
    
    int main (int argc, char * argv[])
    {
      mr_td_t * tdp = mr_get_td_by_name ("fruit_t");
    
      if (tdp)
        {
          int i;
          for (i = 0; i < tdp->fields_size / sizeof (tdp->fields[0]); ++i)
            printf ("[%" SCNd64 "] = %s\n", tdp->fields[i].fdp->param.enum_value, tdp->fields[i].fdp->name.str);
        }
      return (EXIT_SUCCESS);
    }
    

    此程序将输出

    $ ./enum
    [0] = Apple
    [1] = Banana
    [2] = Pear
    [3] = Tomato
    

    图书馆为最新的GCC和Clang工作良好。

        10
  •  1
  •   roalz Steve Townsend    8 年前

    也有 Better Enums ,它是一个只需要C++ 11的头文件库(文件),并在BSD软件许可证下进行许可。官方说明:

    C+的反射编译时枚举:BetterEnums是一个轻量级的头文件,它使编译器生成反射枚举类型。

    以下是官网的代码示例:

    #include <enum.h>
    
    BETTER_ENUM(Channel, int, Red = 1, Green, Blue)
    
    Channel     c = Channel::_from_string("Red");
    const char  *s = c._to_string();
    
    size_t      n = Channel::_size();
    for (Channel c : Channel::_values()) {
        run_some_function(c);
    }
    
    switch (c) {
        case Channel::Red:    // ...
        case Channel::Green:  // ...
        case Channel::Blue:   // ...
    }
    
    Channel     c = Channel::_from_integral(3);
    
    constexpr Channel c =
        Channel::_from_string("Blue");
    

    它看起来很有前途,尽管我还没有测试过。此外,C++有很多(自定义的)反射库。我希望类似的东西 更好的枚举 迟早会成为标准模板库(STL)的一部分(或者至少是Boost)。