C/C++:有什么方法可以获得反射枚举吗?

发布于 2024-08-12 03:22:03 字数 786 浏览 3 评论 0原文

我已经多次遇到这种情况了......

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

显然这是一个混乱的解决方案。如果开发人员在标题中添加了新的水果,并且没有在 Fruits[] 中添加新的条目(不能怪他,它们必须位于两个不同的文件中!),应用程序就会繁荣起来。

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

PS:这与“针对所有内容”的反射相反,在编译器中实现起来非常简单。看到这个问题有多常见(至少对我来说),我真的不敢相信没有反射枚举 Fruit.. 即使在 C++0x 中也没有。

PS2:我使用的是C++,但我也将这个问题标记为C,因为C也有同样的问题。如果您的解决方案仅包含 C++ 内容,那对我来说没问题。

I've encountered this situation so many times...

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

Now I have Fruit f; // banana and I want to go from f to the string "Banana"; or I have string s = "Banana" and from that I want to go to Banana // enum value or int.

So far I've been doing this.. Assuming the enum is in 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.

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

PS2: I'm using C++ but I tagged this question as C as well because C has the same problem. If your solution includes C++ only things, that's ok for me.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(10

顾忌 2024-08-19 03:22:03

这需要在外部文件中定义水果。
这将是 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

这将是 fruit-defs.h

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

只要值从 0 开始并且连续,它就可以工作......

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

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

This one requires the fruits to be defined in an external file.
This would be the content of 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...

Update: mix this solution with the one from Richard Pennington using C99 if you need non-consecutive values. 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
不醒的梦 2024-08-19 03:22:03

我发现的一种 c99 方法有助于减少错误:

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

您可以添加枚举,即使在中间,也不会破坏旧的定义。当然,您仍然可以为您忘记的值获取 NULL 字符串。

A c99 way that I've found helps reduce mistakes:

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.

心如荒岛 2024-08-19 03:22:03

我过去做过的一个技巧是添加一个额外的枚举,然后执行编译时断言(例如 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);

这至少可以防止有人忘记添加到枚举和名称数组中,并让他们知道一旦他们尝试编译。

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:

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.

耶耶耶 2024-08-19 03:22:03

对宏解决方案的一条评论 - 您不需要为枚举器提供单独的文件。只需使用另一个宏:(

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

不过,我可能会省略逗号,并根据需要将它们合并到 FRUIT 宏中。)

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

层林尽染 2024-08-19 03:22:03

还有Better Enums,这是一个需要C+的head-only库(文件) +11 并根据 BSD 软件许可证获得许可。官方说明:

C+ 的反射编译时枚举:Better Enums 是一个单一的轻量级头文件,可让您的编译器生成反射枚举类型。

这是来自官方网站的代码示例:

#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++ 的(自定义)反射库。我希望类似 Better Enums 的东西迟早会成为标准模板库 (STL)(或至少是 Boost)的一部分。

There is also Better Enums, which is a head-only library (file) that requires C++11 and is licensed under the BSD software license. Official description:

Reflective compile-time enums for C+: Better Enums is a single, lightweight header file that makes your compiler generate reflective enum types.

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++. I hope something similar to Better Enums will be part of the Standard Template Library (STL) (or at least Boost), sooner or later.

往事风中埋 2024-08-19 03:22:03

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

enum Fruit {
  Apple,
  Banana,
  NumFruits
};

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

然后,如果您向 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.

So it protects you from having the array be the wrong size, but it doesn't help you ensure that the strings are correct.

星光不落少年眉 2024-08-19 03:22:03

看一下 Metaresc 库 https://github.com/alexanderchuranov/Metaresc

它提供了类型的接口还将生成该类型的元数据的声明。基于元数据,您可以轻松地序列化/反序列化任何复杂性的对象。开箱即用,您可以序列化/反序列化 XML、JSON、YAML、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, count = tdp->param.enum_param.enums_size / sizeof (tdp->param.enum_param.enums[0]);
      for (i = 0; i < count; ++i)
        {
          mr_ed_t * edp = tdp->param.enum_param.enums[i];
          MR_PRINT ("[", edp->value._unsigned,  "] = ", edp->name.str, "\n");
        }        
    }
  return (EXIT_SUCCESS);
}

这个程序将输出

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

Library Works Fine forlatest gcc and clang。

Take a look at Metaresc library 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, YAML, XDR, Lisp-like notation, C-init notation.

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, count = tdp->param.enum_param.enums_size / sizeof (tdp->param.enum_param.enums[0]);
      for (i = 0; i < count; ++i)
        {
          mr_ed_t * edp = tdp->param.enum_param.enums[i];
          MR_PRINT ("[", edp->value._unsigned,  "] = ", edp->name.str, "\n");
        }        
    }
  return (EXIT_SUCCESS);
}

This program will output

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

Library works fine for latest gcc and clang.

二手情话 2024-08-19 03:22:03

可以为它创建一个类结构:

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

然后你可以有一个接受 Fruit 的函数,它甚至会更加类型安全。

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

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

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. But more than likely that doesn't matter.

心是晴朗的。 2024-08-19 03:22:03

正如回答该问题的其他人所表明的那样,仅使用 C 预处理器并没有真正干净(“DRY”)的方法来完成此操作。问题是您需要定义一个枚举大小的数组,其中包含与每个枚举值对应的字符串,而 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. 

%

Here %'s mark delimiters.

然后,一个 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 自动构建状态字符串文件。

As the other people answering the question have shown, there isn't really a clean ("D.R.Y.") way to do this using the C preprocessor alone. 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. 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,

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:

/* 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. (There are twenty-six possible statuses in fact.)

Then the construction of the status string file is automated using make.

葵雨 2024-08-19 03:22:03

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

就我个人而言,我选择了一个自定义类来包装我的枚举。目标是提供比传统枚举更多的功能(例如迭代)。

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

当然,问题在于定义,因为我必须首先声明枚举,然后映射它......但这就是您使用它们所付出的代价。

另外,我然后不使用真正的枚举,而是使用指向映射(在幕后)的 const_iterator 来表示枚举值(end 表示无效值)。

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

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文