是否有编译时方法来检测/防止C/C++枚举中的重复值?
问题在于有多个项目被初始化为显式值.
背景:
我继承了一些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;
Run Code Online (Sandbox Code Playgroud)
问题是随着代码的增长和开发人员向MsgFoo1x
组中添加更多消息,最终它会超出BASE2_VAL
.
这段代码最终会迁移到C++,所以如果只有一个C++解决方案(模板魔术?),那没关系 - 但是使用C和C++的解决方案更好.
Die*_*Epp 14
有几种方法可以检查这个编译时间,但它们可能并不总是适合您.首先在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;
Run Code Online (Sandbox Code Playgroud)
现在我们需要一种方法来确保MARKER_1_DONT_USE < BASE2_VAL
在编译时.有两种常见的技术.
声明具有负大小的数组是错误的.这看起来有点难看,但它确实有效.
extern int IGNORE_ENUM_CHECK[MARKER_1_DONT_USE > BASE2_VAL ? -1 : 1];
Run Code Online (Sandbox Code Playgroud)
如果MARKER_1_DONT_USE大于BASE_2_VAL,几乎每个编写的编译器都会生成错误.海湾合作委员会吐出:
test.c:16: error: size of array ‘IGNORE_ENUM_CHECK’ is negative
Run Code Online (Sandbox Code Playgroud)
如果您的编译器支持C11,您可以使用_Static_assert
.对C11的支持并不普遍,但您的编译器可能_Static_assert
仍然支持,特别是因为C++中的相应功能得到了广泛支持.
_Static_assert(MARKER_1_DONT_USE < BASE2_VAL, "Enum values overlap.");
Run Code Online (Sandbox Code Playgroud)
GCC吐出以下消息:
test.c:16:1: error: static assertion failed: "Enum values overlap."
_Static_assert(MARKER_1_DONT_USE < BASE2_VAL, "Enum values overlap.");
^
Run Code Online (Sandbox Code Playgroud)
我没有在您的要求中看到"漂亮",所以我提交了使用Boost预处理器库实现的解决方案.
作为一个前期免责声明,我没有使用Boost.Preprocessor很多,我只用这里介绍的测试用例测试了这个,所以可能有bug,并且可能有一个更容易,更清晰的方法来做到这一点.我当然欢迎评论,更正,建议,侮辱等.
开始了:
#include <boost/preprocessor.hpp>
#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) \
} \
} \
}
Run Code Online (Sandbox Code Playgroud)
然后我们可以像这样使用它:
DEFINE_UNIQUE_ENUM(DayOfWeek, ((Monday) (1))
((Tuesday) (2))
((Wednesday) )
((Thursday) (4)))
Run Code Online (Sandbox Code Playgroud)
枚举器值是可选的; 此代码生成一个等效于的枚举:
enum DayOfWeek
{
Monday = 1,
Tuesday = 2,
Wednesday,
Thursday = 4
};
Run Code Online (Sandbox Code Playgroud)
它还会生成一个完整性检查功能,其中包含Ben Voigt的答案中描述的switch语句.如果我们更改枚举声明,以便我们有非唯一的枚举值,例如,
DEFINE_UNIQUE_ENUM(DayOfWeek, ((Monday) (1))
((Tuesday) (2))
((Wednesday) )
((Thursday) (1)))
Run Code Online (Sandbox Code Playgroud)
它不会编译(Visual C++报告预期的错误C2196:已使用案例值'1').
还要感谢Matthieu M.,他对另一个问题的回答让我对Boost预处理器库感兴趣.