C中的Typesafe枚举?

如果我有多个enum ,例如:

  enum Greetings{ hello, bye, how }; enum Testing { one, two, three }; 

如何强制使用正确的enum ? 例如,我不希望有人在使用hello时使用hello以获得更好的调试和可读性。

在C中,您可以使用样板代码伪造它。

 typedef enum { HELLO_E, GOODBYE_E } greetings_t; struct greetings { greetings_t greetings; }; #define HELLO ((struct greetings){HELLO_E}) #define GOODBYE ((struct greetings){GOODBYE_E}) typedef enum { ONE_E, TWO_E } number_t; struct number { number_t number; }; #define ONE ((struct number){ONE_E}) #define TWO ((struct number){TWO_E}) void takes_greeting(struct greetings g); void takes_number(struct number n); void test() { takes_greeting(HELLO); takes_number(ONE); takes_greeting(TWO); takes_number(GOODBYE); } 

这不应该产生任何开销,并产生错误而不是警告:

 $ gcc -c -std = c99 -Wall -Wextra test2.c
 test2.c:在函数'test'中:
 test2.c:19:错误:'takes_greeting'的参数1的不兼容类型
 test2.c:20:错误:'takes_number'的参数1的不兼容类型

请注意,我没有使用GNU扩展,也没有生成虚假警告。 只有错误。 另请注意,我使用的是一种与泥土一样古老的GCC版本,

 $ gcc --version
 powerpc-apple-darwin9-gcc-4.0.1(GCC)4.0.1(Apple Inc. build 5493)
版权所有(C)2005 Free Software Foundation,Inc。
这是免费软件; 查看复制条件的来源。 没有
保证; 甚至不适用于适销性或特定用途的适用性。

这适用于任何支持C99复合文字的编译器。

Clang产生以下警告,这是您可以做的最好的事情(尽管用户可以将警告升级为错误)。

 enum Greetings { hello, bye, how }; enum Count { one, two, three }; void takes_greeting(enum Greetings x) {} void takes_count(enum Count x) {} int main() { takes_greeting(one); takes_count(hello); } 

编译器输出:

 cc foo.c -o foo foo.c:8:17: warning: implicit conversion from enumeration type 'enum Count' to different enumeration type 'enum Greetings' [-Wenum-conversion] takes_greeting(one); ~~~~~~~~~~~~~~ ^~~ foo.c:9:14: warning: implicit conversion from enumeration type 'enum Greetings' to different enumeration type 'enum Count' [-Wenum-conversion] takes_count(hello); ~~~~~~~~~~~ ^~~~~ 

如果用户要忽略编译器中的错误和警告,那么您无法帮助他们。

不幸的是, enum是C类型系统中的一个弱点。 enum类型的变量属于enum类型,但是用enum声明的常量是int类型。

所以在你的例子中

 enum Greetings{ hello, bye, how }; enum Testing { one, two, three }; enum Greetings const holla = hello; enum Testing const eins = one; 

helloone是相同值的两个名称,即0 ,并且类型为int

hollaeins再次具有值0 ,但具有各自的类型。

如果你想为“真正的”常量强制一些“官方”类型安全,那就是具有你想要的类型和值的实体,你必须使用一些更复杂的结构:

 #define GREETING(VAL) ((enum Greetings){ 0 } = (VAL)) #define HELLO GREETING(hello) 

GREETING宏中的赋值确保结果为“rvalue”,因此无法对其进行修改,编译器将仅针对其类型和值进行修改。

这是你不想听到的答案。 在C中,你不能真的。 现在,如果你的C代码在C ++的“Clean C”子集中,你可以使用C ++编译器进行编译,以获得使用错误的enum / int值等的所有错误。

如果你还想确保有效范围,那么有一种技术可以带来指针取消引用的小开销,用于获取整数值 – 以及大量的样板类型。 它可能仍然有用,因为它可以在编写范围检查代码时利用它,否则它将是必要的。

greetings.h:

 #ifndef GREETINGS_H #define GREETINGS_H struct greetings; typedef struct greetings Greetings; extern const Greetings * const Greetings_hello; extern const Greetings * const Greetings_bye; extern const Greetings * const Greetings_how; const char *Greetings_str(const Greetings *g); int Greetings_int(const Greetings *g); #endif 

greetings.c:

 #include "greetings.h" struct greetings { const int val; }; static const Greetings hello = { 0 }; static const Greetings bye = { 1 }; static const Greetings how = { 2 }; const Greetings * const Greetings_hello = &hello; const Greetings * const Greetings_bye = &bye; const Greetings * const Greetings_how = &how; static const char * const Greetings_names[] = { "hello", "bye", "how" }; const char * Greetings_str(const Greetings *g) { return Greetings_names[g->val]; } int Greetings_int(const Greetings *g) { return g->val; } 

示例main.c:

 #include  #include "greetings.h" void printTest(const Greetings *greeting) { if (greeting == Greetings_how) return; puts(Greetings_str(greeting)); } int main() { const Greetings *g = Greetings_hello; printTest(g); } 

是的,输入很多,但你可以获得完整的类型和范围安全性。 没有其他编译单元可以实例化struct greetings ,因此您完全安全。


编辑2015-07-04:为了防止NULL,有两种可能性。 使用NULL作为默认值( #define Greetings_hello 0而不是现在使用的指针)。 这非常方便,但默认枚举值的类型安全性,NULL可用于任何枚举。 或者声明它无效并在访问器方法中检查它,返回错误,或使用类似GCC __attribute__((nonnull()))来在编译时捕获它,例如在greetings.h中:

 const char *Greetings_str(const Greetings *g) __attribute__((nonnull(1))); 

您可以键入自己的枚举,然后声明这些类型的变量和函数参数。