简体   繁体   English

C / C ++:任何获得反思枚举的方法?

[英]C/C++: any way to get reflective enums?

I've encountered this situation so many times... 我遇到过这种情况很多次......

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

Now I have Fruit f; // banana 现在我有Fruit f; // banana Fruit f; // banana and I want to go from f to the string "Banana" ; Fruit f; // banana ,我想从f到字符串"Banana" ; or I have string s = "Banana" and from that I want to go to Banana // enum value or int . 或者我有string s = "Banana" ,从那里我想去Banana // enum value or int

So far I've been doing this.. Assuming the enum is in Fruit.h: 到目前为止,我一直这样做..假设枚举在Fruit.h中:

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

Obviously that's a messy solution. 显然这是一个混乱的解决方案。 If a developer adds a new fruit to the header and doesn't add a new entry in Fruits[] (can't blame him, they have to be in two different files!) the application goes boom. 如果开发人员在标题中添加了新的水果,并且没有在Fruits []中添加新条目(不能责怪他,他们必须在两个不同的文件中!)应用程序会蓬勃发展。

Is there a simple way to do what I want, where everything is in one file? 有没有一种简单的方法可以做我想要的,一切都在一个文件中? Preprocessor hacks, alien magic, anything.. 预处理器黑客,外星魔法,任何东西..

PS: This, contrary to reflection "for everything", would be really trivial to implement in compilers. PS:这与“为了一切”的反思相反,在编译器中实现起来真的很微不足道。 Seeing how common a problem it is (at least for me) I really can't believe there is no reflective enum Fruit .. Not even in C++0x. 看到问题有多常见(至少对我而言)我真的不敢相信没有reflective enum Fruit ......即使在C ++ 0x中也是如此。

PS2: I'm using C++ but I tagged this question as C as well because C has the same problem. PS2:我正在使用C ++,但我也将这个问题标记为C,因为C有同样的问题。 If your solution includes C++ only things, that's ok for me. 如果您的解决方案仅包含C ++,那对我来说没问题。

This one requires the fruits to be defined in an external file. 这个需要在外部文件中定义水果。 This would be the content of fruit.cpp : 这将是fruit.cpp的内容:

#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

And this would be fruit-defs.h : 这将是水果定义

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

It works as long as the values start in 0 and are consecutive... 只要值从0开始并且是连续的,它就可以工作......

Update: mix this solution with the one from Richard Pennington using C99 if you need non-consecutive values. 更新:如果您需要非连续值,请使用C99将此解决方案与Richard Pennington的解决方案混合使用。 Ie, something like: 即,类似于:

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

A c99 way that I've found helps reduce mistakes: 我发现的c99方式有助于减少错误:

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

You can add enums, even in the middle, and not break old definitions. 您可以添加枚举,即使是在中间,也不要破坏旧定义。 You can still get NULL strings for values you forget, of course. 当然,您仍然可以为忘记的值获取NULL字符串。

One trick I've done in the past is to add an extra enum and then do a compile time assert (such as Boost's ) to make sure the two are kept in sync: 我过去做的一个技巧是添加一个额外的枚举,然后执行编译时断言(例如Boost )以确保两者保持同步:

enum Fruit {
    APPLE,
    BANANA,

    // MUST BE LAST ENUM
    LAST_FRUIT
};

const char *FruitNames[] =
{
    "Apple",
    "Banana",
};

BOOST_STATIC_ASSERT((sizeof(FruitNames) / sizeof(*FruitNames)) == LAST_FRUIT);

This will at least prevent someone from forgetting to add to both the enum and the name array and will let them know as soon as they try to compile. 这至少可以防止有人忘记添加枚举和名称数组,并且一旦他们尝试编译就会让他们知道。

One comment on the macro solution - you don't need a separate file for the enumerators. 关于宏解决方案的一个评论 - 您不需要为枚举器提供单独的文件。 Just use another macro: 只需使用另一个宏:

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

(I would probably leave the commas out, though, and incorporate them into the FRUIT macro as needed.) (不过,我可能会留下逗号,并根据需要将它们合并到FRUIT宏中。)

What if you did something like this? 如果你这样做了怎么办?

enum Fruit {
  Apple,
  Banana,
  NumFruits
};

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

Then if you add a new entry to the Fruit enum, your compiler should complain that there are insufficient entries in the initializer of the array, so you would be forced to add an entry to the array. 然后,如果向Fruit枚举添加新条目,则编译器应该抱怨数组的初始化程序中没有足够的条目,因此您将被迫向数组添加条目。

So it protects you from having the array be the wrong size, but it doesn't help you ensure that the strings are correct. 因此,它可以保护您免受阵列大小错误的影响,但它不能帮助您确保字符串是正确的。

As the other people answering the question have shown, there isn't really a clean ("DRY") way to do this using the C preprocessor alone. 正如其他人回答这个问题所表明的那样,单独使用C预处理器并没有真正干净(“干”)的方法。 The problem is that you need to define an array of size of your enum containing strings corresponding to each enum value, and the C preprocessor isn't smart enough to be able to do that. 问题是你需要定义包含与每个枚举值对应的字符串的枚举大小的数组,并且C预处理器不够智能,无法做到这一点。 What I do is to create a text file something like this: 我所做的是创建一个这样的文本文件:

%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. 

%

Here %'s mark delimiters. 这里是%的标记分隔符。

Then a Perl script, the working part of which looks like this, 然后是一个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;
}

reads this file and writes a header file: 读取此文件并写入头文件:

typedef enum kinopiko_status {
    kinopiko_status_ok,
    kinopiko_status_eof_reading_content,

and a C file: 和一个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. ",

using the input file at the top. 使用顶部的输入文件。 It also calculates the number 26 here by counting the input lines. 它还通过计算输入线来计算数字26。 (There are twenty-six possible statuses in fact.) (事实上​​,有26种可能的状态。)

Then the construction of the status string file is automated using make . 然后使用make自动构建状态字符串文件。

Could make a class structure for it: 可以为它创建一个类结构:

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

Then you can have a function that takes a Fruit, and it will even be more type safe. 然后你可以拥有一个带有水果的功能,它甚至会更安全。

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

The sizeof this is 2x bigger than just an enum. 它的大小比枚举大2倍。 But more than likely that doesn't matter. 但这很可能无关紧要。

I don't like macro solutions, in general, though I admit it's kind of difficult there to avoid them. 一般来说,我不喜欢宏观解决方案,但我承认在那里避免它们很困难。

Personally I opted for a custom class to wrap my enums in. The goal was to offer a bit more that traditional enums (like iteration). 我个人选择了一个自定义类来包装我的枚举。目标是提供更多传统的枚举(如迭代)。

Under the cover, I use a std::map to map the enum to its std::string counterpart. 在封面下,我使用std::map map将枚举映射到它的std::string对应物。 Then I can use this to both iterate over the enum and "pretty print" my enum or initialize it from a string read in a file. 然后我可以使用它来迭代枚举和“漂亮打印”我的枚举或从文件中读取的字符串初始化它。

The problem, of course, is the definition, since I have to first declare the enum and then map it... but that's the price you pay for using them. 当然,问题在于定义,因为我必须首先声明枚举然后映射它......但这是你使用它们所付出的代价。

Also, I then use not a real enum, but a const_iterator pointing to the map (under the covers) to represent the enum value (with end representing an invalid value). 此外,我然后使用的不是真正的枚举,而是指向地图(在封面下)的const_iterator来表示枚举值( end表示无效值)。

Take a look at Metaresc library https://github.com/alexanderchuranov/Metaresc 看看Metaresc库https://github.com/alexanderchuranov/Metaresc

It provides interface for types declaration that will also generate meta-data for the type. 它为类型声明提供了接口,该接口也将为该类型生成元数据。 Based on meta-data you can easily serialize/deserialize objects of any complexity. 基于元数据,您可以轻松地序列化/反序列化任何复杂的对象。 Out of the box you can serialize/deserialize XML, JSON, XDR, Lisp-like notation, C-init notation. 开箱即用,您可以序列化/反序列化XML,JSON,XDR,类似Lisp的表示法,C-init表示法。

Here is a simple example: 这是一个简单的例子:

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

This program will output 该程序将输出

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

Library works fine for latest gcc and clang. 图书馆适用于最新的gcc和clang。

There is also Better Enums , which is a head-only library (file) that requires C++11 and is licensed under the BSD software license. 还有Better Enums ,它是一个仅限头部的库(文件),需要C ++ 11并且在BSD软件许可下获得许可。 Official description: 官方说明:

Reflective compile-time enums for C+: Better Enums is a single, lightweight header file that makes your compiler generate reflective enum types. C +的反射编译时枚举:Better Enums是一个单一的轻量级头文件,它使编译器生成反射枚举类型。

Here is the code example from the official website: 以下是官方网站的代码示例:

#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");

It looks very promising, though I haven't tested it yet. 它看起来非常有前途,但我还没有测试过。 In addition there are plenty of (custom) reflection libraries for C++. 此外,还有很多用于C ++的(自定义)反射库。 I hope something similar to Better Enums will be part of the Standard Template Library (STL) (or at least Boost), sooner or later. 我希望类似于Better Enums的东西迟早会成为标准模板库(STL)(或至少是Boost)的一部分。

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

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