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

时间:2010-04-05 04:04:42

标签: c++ c enums

是否有编译时方法来检测/防止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;

问题在于随着代码的增长而增长当开发人员向MsgFoo1x群组添加更多邮件时,最终会超出BASE2_VAL

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

8 个答案:

答案 0 :(得分: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;

现在我们需要一种方法来确保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 ++中的相应功能得到了广泛支持。

_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.");
 ^

答案 1 :(得分:7)

我没有在您的要求中看到“漂亮”,因此我提交了使用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)  \
        }                                                                 \
    }                                                                     \
}

我们可以这样使用它:

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

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

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

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

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

它将无法编译(Visual C ++报告预期的错误C2196:已使用案例值'1')。

还要感谢Matthieu M.,whose answer to another question让我对Boost预处理器库感兴趣。

答案 2 :(得分:3)

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

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

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

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

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

答案 3 :(得分:3)

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

switch (0) {
    case MsgFoo1A: break;
    case MsgFoo1B: break;
    case MsgFoo1C: break;
    case MsgFoo1D: break;
    case MsgFoo1E: break;
    case MsgFoo2A: break;
    case MsgFoo2B: break;
}
如果重用任何积分值,

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

答案 4 :(得分:1)

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

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

另一种方法可能是使用类似gccxml(或更舒适pygccxml)的方法来识别人工检查的候选人。

答案 5 :(得分:1)

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

某处宣布:

enum E { A = 0, B = 0 };
在其他地方,我们建造这种机器:

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


template<typename S, S... s>
struct is_distinct : std::true_type {};

template<typename S, S s0, S... s>
struct is_distinct : std::integral_constant< bool,
  std::is_distinct<S, s...>::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)工作,因此对于极大的枚举,这可能会导致问题。 O(lg(n))深度和O(n lg(n))与更大的常数因子一起工作可以通过首先对元素列表进行排序来完成,但这是更多的工作。

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

答案 6 :(得分:0)

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

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

为了清理,我在How can I avoid repeating myself when creating a C++ enum and a dependent data structure?

使用了aaronps的回答

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

#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时崩溃。

答案 7 :(得分:0)

这是使用X macro而不使用Boost的解决方案。首先定义X宏及其助手宏。我正在使用this solution来为X宏进行2个重载,以便您可以定义带或不带显式值的枚举。如果您使用的是GCC或Clang,则可以使其更短

#define COUNT_X_ARGS_IMPL2(_1, _2, count, ...) count
#define COUNT_X_ARGS_IMPL(args) COUNT_X_ARGS_IMPL2 args
#define COUNT_X_ARGS(...) COUNT_X_ARGS_IMPL((__VA_ARGS__, 2, 1, 0))

/* Pick the right X macro to invoke. */
#define X_CHOOSE_HELPER2(count) X##count
#define X_CHOOSE_HELPER1(count) X_CHOOSE_HELPER2(count)
#define X_CHOOSE_HELPER(count)  X_CHOOSE_HELPER1(count)

/* The actual macro. */
#define X_GLUE(x, y) x y
#define X(...) X_GLUE(X_CHOOSE_HELPER(COUNT_X_ARGS(__VA_ARGS__)), (__VA_ARGS__))

然后定义宏并进行检查

#define BASE1_VAL    (5)
#define BASE2_VAL    (7)

// Enum values
#define MY_ENUM             \
X(MsgFoo1A, BASE1_VAL)      \
X(MsgFoo1B)                 \
X(MsgFoo1C)                 \
X(MsgFoo1D)                 \
X(MsgFoo1E)                 \
X(MsgFoo2A, BASE2_VAL)      \
X(MsgFoo2B)

// Define the enum
#define X1(enum_name)               enum_name,
#define X2(enum_name, enum_value)   enum_name = enum_value,
enum foo
{
    MY_ENUM
};
#undef X1
#undef X2

// Check duplicates
#define X1(enum_name)               case enum_name: break;
#define X2(enum_name, enum_value)   case enum_name: break;
static void check_enum_duplicate()
{
    switch(0)
    {
        MY_ENUM
    }
}
#undef X1
#undef X2

使用

int main()
{
// Do something with the whole enum
#define X1(enum_name)               printf("%s = %d\n", #enum_name, enum_name);
#define X2(enum_name, enum_value)   printf("%s = %d\n", #enum_name, enum_value);
    // Print the whole enum
    MY_ENUM
#undef X1
#undef X2
}