[英]C/C++: any way to get reflective enums?
我遇到过这种情况很多次......
enum Fruit {
Apple,
Banana,
Pear,
Tomato
};
现在我有Fruit f; // banana
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:这与“为了一切”的反思相反,在编译器中实现起来真的很微不足道。 看到问题有多常见(至少对我而言)我真的不敢相信没有reflective enum Fruit
......即使在C ++ 0x中也是如此。
PS2:我正在使用C ++,但我也将这个问题标记为C,因为C有同样的问题。 如果您的解决方案仅包含C ++,那对我来说没问题。
这个需要在外部文件中定义水果。 这将是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(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
我发现的c99方式有助于减少错误:
enum Fruit {
APPLE,
BANANA
};
const char* Fruits[] = {
[APPLE] = "APPLE",
[BANANA] = "BANANA"
};
您可以添加枚举,即使是在中间,也不要破坏旧定义。 当然,您仍然可以为忘记的值获取NULL字符串。
我过去做的一个技巧是添加一个额外的枚举,然后执行编译时断言(例如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);
这至少可以防止有人忘记添加枚举和名称数组,并且一旦他们尝试编译就会让他们知道。
关于宏解决方案的一个评论 - 您不需要为枚举器提供单独的文件。 只需使用另一个宏:
#define FRUITs \
FRUIT(Banana), \
FRUIT(Apple), \
FRUIT(Pear), \
FRUIT(Tomato)
(不过,我可能会留下逗号,并根据需要将它们合并到FRUIT宏中。)
如果你这样做了怎么办?
enum Fruit {
Apple,
Banana,
NumFruits
};
const char *Fruits[NumFruits] = {
"Apple",
"Banana",
};
然后,如果向Fruit枚举添加新条目,则编译器应该抱怨数组的初始化程序中没有足够的条目,因此您将被迫向数组添加条目。
因此,它可以保护您免受阵列大小错误的影响,但它不能帮助您确保字符串是正确的。
正如其他人回答这个问题所表明的那样,单独使用C预处理器并没有真正干净(“干”)的方法。 问题是你需要定义包含与每个枚举值对应的字符串的枚举大小的数组,并且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
自动构建状态字符串文件。
可以为它创建一个类结构:
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倍。 但这很可能无关紧要。
一般来说,我不喜欢宏观解决方案,但我承认在那里避免它们很困难。
我个人选择了一个自定义类来包装我的枚举。目标是提供更多传统的枚举(如迭代)。
在封面下,我使用std::map
map将枚举映射到它的std::string
对应物。 然后我可以使用它来迭代枚举和“漂亮打印”我的枚举或从文件中读取的字符串初始化它。
当然,问题在于定义,因为我必须首先声明枚举然后映射它......但这是你使用它们所付出的代价。
此外,我然后使用的不是真正的枚举,而是指向地图(在封面下)的const_iterator来表示枚举值( end
表示无效值)。
看看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。
还有Better Enums ,它是一个仅限头部的库(文件),需要C ++ 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)的一部分。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.