C / C ++枚举:检测多个项目映射到相同值的时间

是否有编译时方法来检测/防止C / C ++枚举中的重复值?

问题在于有多个项目被初始化为显式值

背景:

我inheritance了一些C代码,如下所示:

#define BASE1_VAL (5) #define BASE2_VAL (7) typedef enum { MsgFoo1A = BASE1_VAL, // 5 MsgFoo1B, // 6 MsgFoo1C, // 7 MsgFoo1D, // 8 MsgFoo1E, // 9 MsgFoo2A = BASE2_VAL, // Uh oh! 7 again... MsgFoo2B // Uh oh! 8 again... } FOO; 

问题是随着代码的增长以及开发人员向MsgFoo1x组添加更多消息,最终它会超出BASE2_VAL

这段代码最终将被迁移到C ++,所以如果只有一个C ++解决方案(模板魔术?),那没关系 – 但是一个适用于C和C ++的解决方案更好。

有几种方法可以检查这个编译时间,但它们可能并不总是适合您。 首先在MsgFoo2A之前插入一个“marker”枚举值。

 typedef enum { MsgFoo1A = BASE1_VAL, MsgFoo1B, MsgFoo1C, MsgFoo1D, MsgFoo1E, MARKER_1_DONT_USE, /* Don't use this value, but leave it here. */ MsgFoo2A = BASE2_VAL, MsgFoo2B } FOO; 

现在我们需要一种方法来确保MARKER_1_DONT_USE < BASE2_VAL在编译时。 有两种常见的技术。

负大小的数组

声明具有负大小的数组是错误的。 这看起来有点难看,但它确实有效。

 extern int IGNORE_ENUM_CHECK[MARKER_1_DONT_USE > BASE2_VAL ? -1 : 1]; 

如果MARKER_1_DONT_USE大于BASE_2_VAL,几乎每个编写的编译器都会生成错误。 海湾合作委员会吐出:

 test.c:16: error: size of array 'IGNORE_ENUM_CHECK' is negative 

静态断言

如果您的编译器支持C11,则可以使用_Static_assert 。 对C11的支持并不是普遍存在,但是无论如何你的编译器都可能支持_Static_assert ,特别是因为C ++中相应的function得到了广泛的支持。

 _Static_assert(MARKER_1_DONT_USE < BASE2_VAL, "Enum values overlap."); 

GCC吐出以下消息:

 test.c:16:1: error: static assertion failed: "Enum values overlap." _Static_assert(MARKER_1_DONT_USE < BASE2_VAL, "Enum values overlap."); ^ 

我没有在您的要求中看到“漂亮”,因此我提交了使用Boost预处理器库实现的此解决方案。

作为一个前期免责声明,我没有使用Boost.Preprocessor很多,我只用这里介绍的测试用例测试了这个,所以可能有bug,并且可能有一种更简单,更清晰的方法来做到这一点。 我当然欢迎评论,更正,建议,侮辱等。

开始了:

 #include  #define EXPAND_ENUM_VALUE(r, data, i, elem) \ BOOST_PP_SEQ_ELEM(0, elem) \ BOOST_PP_IIF( \ BOOST_PP_EQUAL(BOOST_PP_SEQ_SIZE(elem), 2), \ = BOOST_PP_SEQ_ELEM(1, elem), \ BOOST_PP_EMPTY()) \ BOOST_PP_COMMA_IF(BOOST_PP_NOT_EQUAL(data, BOOST_PP_ADD(i, 1))) #define ADD_CASE_FOR_ENUM_VALUE(r, data, elem) \ case BOOST_PP_SEQ_ELEM(0, elem) : break; #define DEFINE_UNIQUE_ENUM(name, values) \ enum name \ { \ BOOST_PP_SEQ_FOR_EACH_I(EXPAND_ENUM_VALUE, \ BOOST_PP_SEQ_SIZE(values), values) \ }; \ \ namespace detail \ { \ void UniqueEnumSanityCheck##name() \ { \ switch (name()) \ { \ BOOST_PP_SEQ_FOR_EACH(ADD_CASE_FOR_ENUM_VALUE, name, values) \ } \ } \ } 

然后我们可以像这样使用它:

 DEFINE_UNIQUE_ENUM(DayOfWeek, ((Monday) (1)) ((Tuesday) (2)) ((Wednesday) ) ((Thursday) (4))) 

枚举器值是可选的; 此代码生成一个等效于的枚举:

 enum DayOfWeek { Monday = 1, Tuesday = 2, Wednesday, Thursday = 4 }; 

它还会生成一个完整性检查function,其中包含Ben Voigt的答案中描述的switch语句。 如果我们更改枚举声明,以便我们有非唯一的枚举值,例如,

 DEFINE_UNIQUE_ENUM(DayOfWeek, ((Monday) (1)) ((Tuesday) (2)) ((Wednesday) ) ((Thursday) (1))) 

它不会编译(Visual C ++报告预期的错误C2196:已使用案例值’1′ )。

还要感谢Matthieu M., 他对另一个问题的回答让我对Boost预处理器库感兴趣。

我不相信有一种方法可以用语言本身来检测这一点,考虑到可以想象你需要两个枚举值相同的情况。 但是,您始终可以确保所有显式设置的项都位于列表的顶部:

 typedef enum { MsgFoo1A = BASE1_VAL, // 5 MsgFoo2A = BASE2_VAL, // 7 MsgFoo1B, // 8 MsgFoo1C, // 9 MsgFoo1D, // 10 MsgFoo1E, // 11 MsgFoo2B // 12 } FOO; 

只要指定的值位于顶部,就不会发生冲突,除非由于某种原因,宏扩展为相同的值。

通常,通过为每个MsgFooX组提供固定数量的位来克服此问题,并确保每个组不会溢出它分配的位数。 “位数”解决方案很不错,因为它允许按位测试来确定某些消息组属于哪些消息组。 但是没有内置的语言function来执行此操作,因为有一个枚举的合法案例具有两个相同的值:

 typedef enum { gray = 4, //Gr[ae]y should be the same grey = 4, color = 5, //Also makes sense in some cases couleur = 5 } FOO; 

我不知道会自动检查所有枚举成员的任何内容,但如果您想检查初始化程序(或它们依赖的宏)的未来更改不会导致冲突:

 switch (0) { case MsgFoo1A: break; case MsgFoo1B: break; case MsgFoo1C: break; case MsgFoo1D: break; case MsgFoo1E: break; case MsgFoo2A: break; case MsgFoo2B: break; } 

如果重用任何整数值,将导致编译器错误,并且大多数编译器甚至会告诉您哪个值(数值)是一个问题。

你可以使用Boost.Preprocessor推出一个更强大的定义枚举的解决方案 – 值得花时间是另一回事。

如果您正在转向C ++,也许(建议的)Boost.Enum适合您(可通过Boost Vault获得 )。

另一种方法可能是使用像gccxml (或更舒适的pygccxml )这样的东西来识别手动检查的候选者。

虽然我们没有完全反思,但如果可以重新计算枚举值,则可以解决此问题。

在某处宣布:

 enum E { A = 0, B = 0 }; 

在别处,我们建造这种机器:

 template struct first_not_same_as_rest : std::true_type {}; template struct first_not_same_as_rest : std::integral_constant< bool, (s0 != s1) && first_not_same_as_rest< S, s0, s... >::value > {}; template struct is_distinct : std::true_type {}; template struct is_distinct : std::integral_constant< bool, std::is_distinct::value && first_not_same_as_rest< S, s0, s... >::value > {}; 

一旦你拥有了这个机器(需要C ++ 11),我们可以做到以下几点:

 static_assert( is_distinct< E, A, B >::value, "duplicate values in E detected" ); 

在编译时,我们将确保没有两个元素相等。

这需要编译器在编译时进行O(n)递归深度和O(n ^ 2)工作,因此对于极大的枚举,这可能会导致问题。 AO(lg(n))深度和O(n lg(n))与更大的常数因子一起工作可以通过首先对元素列表进行排序来完成,但这是更多的工作。

使用为C ++ 1y-C ++ 17提出的枚举reflection代码,这将是可行的,而无需重新元素。

我并不完全喜欢这里已经发布的任何答案,但他们给了我一些想法。 关键技术是依靠Ben Voight使用switch语句的答案。 如果交换机中的多个案例共享相同的数字,则会出现编译错误。

对我自己和原始海报最有用,这不需要任何C ++function。

为了清理,我使用了aaronps的答案在创建C ++枚举和依赖数据结构时如何避免重复自己?

首先,在某个地方的某个标题中定义它:

 #define DEFINE_ENUM_VALUE(name, value) name=value, #define CHECK_ENUM_VALUE(name, value) case name: #define DEFINE_ENUM(enum_name, enum_values) \ typedef enum { enum_values(DEFINE_ENUM_VALUE) } enum_name; #define CHECK_ENUM(enum_name, enum_values) \ void enum_name ## _test (void) { switch(0) { enum_values(CHECK_ENUM_VALUE); } } 

现在,只要你需要枚举:

 #define COLOR_VALUES(GEN) \ GEN(Red, 1) \ GEN(Green, 2) \ GEN(Blue, 2) 

最后,这些行需要实际进行枚举:

 DEFINE_ENUM(Color, COLOR_VALUES) CHECK_ENUM(Color, COLOR_VALUES) 

DEFINE_ENUM自己生成枚举数据类型。 CHECK_ENUM创建一个测试函数,用于打开所有枚举值。 如果您有重复项,编译CHECK_ENUM时编译器将崩溃。