检测多个枚举项何时映射到相同值
是否有编译时方法来检测/防止 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++ 的解决方案会更好。
Is there a compile-time way to detect / prevent duplicate values within a C/C++ enumeration?
The catch is that there are multiple items which are initialized to explicit values.
Background:
I've inherited some C code such as the following:
#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;
The problem is that as the code grows & as developers add more messages to the MsgFoo1x
group, eventually it overruns BASE2_VAL
.
This code will eventually be migrated to C++, so if there is a C++-only solution (template magic?), that's OK -- but a solution that works with C and C++ is better.
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(8)
我不知道有什么会自动检查所有枚举成员,但是如果您想检查未来对初始化程序(或它们依赖的宏)的更改不会导致冲突:
如果以下任何一个都将导致编译器错误整数值被重用,大多数编译器甚至会告诉您哪个值(数值)有问题。
I don't know of anything that will automatically check all enum members, but if you want to check that future changes to the initializers (or the macros they rely on) don't cause collisions:
will cause a compiler error if any of the integral values is reused, and most compilers will even tell you what value (the numeric value) was a problem.
您可以使用 Boost 推出更强大的定义枚举的解决方案.预处理器 - 是否值得花时间是另一回事。
如果您无论如何都要转向 C++,也许(建议的)Boost.Enum 适合您(可通过 Boost Vault< /a>)。
另一种方法可能是使用类似 gccxml (或更舒适的 pygccxml)来识别手动检查的候选者。
You could roll a more robust solution of defining enums using Boost.Preprocessor - wether its worth the time is a different matter.
If you are moving to C++ anyway, maybe the (proposed) Boost.Enum suits you (available via the Boost Vault).
Another approach might be to use something like gccxml (or more comfortably pygccxml) to identify candidates for manual inspection.
虽然我们没有完整的反射,但如果您可以重新列出枚举值,您就可以解决这个问题。
在某个地方声明了这一点:
在其他地方,我们构建了这个机制:
一旦拥有该机制(需要 C++11),我们就可以执行以下操作:
并且在编译时我们将确保没有两个元素相等。
这需要 O(n) 递归深度和编译器在编译时的 O(n^2) 工作量,因此对于非常大的枚举,这可能会导致问题。 AO(lg(n)) 深度和 O(n lg(n)) 使用更大的常数因子可以通过首先对元素列表进行排序来完成,但这需要更多的工作。
使用针对 C++1y-C++17 提议的枚举反射代码,无需重新列出元素即可实现此目的。
While we do not have full on reflection, you can solve this problem if you can relist the enumeration values.
Somewhere this is declared:
elsewhere, we build this machinery:
Once you have that machinery (which requires C++11), we can do the following:
and at compile time we will ensure that no two elements are equal.
This requires O(n) recursion depth and O(n^2) work by the compiler at compile time, so for extremely large enums this could cause problems. A O(lg(n)) depth and O(n lg(n)) work with a much larger constant factor can be done by sorting the list of elements first, but that is much, much more work.
With the enum reflection code proposed for C++1y-C++17, this will be doable without relisting the elements.
这是使用 X 宏而不使用 Boost 的解决方案。首先定义 X 宏及其辅助宏。我正在使用 此解决方案 为 X 宏进行可移植的 2 个重载,以便您可以使用或不使用枚举来定义枚举明确的价值。如果您使用 GCC 或 Clang 那么它可以变得更短
然后定义宏并检查它
使用它
Here's a solution using X macro without Boost. First define the X macro and its helper macros. I'm using this solution to portably make 2 overloads for the X macro so that you can define the enum with or without an explicit value. If you're using GCC or Clang then it can be made shorter
Then define the macro and check it
Use it
我并不完全喜欢这里已经发布的任何答案,但他们给了我一些想法。关键技术是依赖 Ben Voight 使用 switch 语句的答案。如果一个 switch 中的多个 case 共享相同的编号,您将收到编译错误。
对我自己和原始发帖人来说最有用的是,这不需要任何 C++ 功能。
为了清理问题,我使用了 aaronps 的答案 创建 C++ 枚举和依赖数据结构时如何避免重复?
首先,在某个标头中的某个位置定义它:
现在,每当您需要枚举时:
最后,这些行是实际进行枚举所必需的:
DEFINE_ENUM
生成枚举数据类型本身。CHECK_ENUM
创建一个打开所有枚举值的测试函数。如果有重复项,编译器在编译CHECK_ENUM
时会崩溃。I didn't completely like any of the answers already posted here, but they gave me some ideas. The crucial technique is to rely on Ben Voight's answer of using a switch statement. If multiple cases in a switch share the same number, you'll get a compile error.
Most usefully to both myself and probably the original poster, this doesn't require any C++ features.
To clean things up, I used aaronps's answer at How can I avoid repeating myself when creating a C++ enum and a dependent data structure?
First, define this in some header someplace:
Now, whenever you need to have an enumeration:
Finally, these lines are required to actually make the enumeration:
DEFINE_ENUM
makes the enum data type itself.CHECK_ENUM
makes a test function that switches on all the enum values. The compiler will crash when compilingCHECK_ENUM
if you have duplicates.有几种方法可以检查编译时间,但它们可能并不总是适合您。首先在 MsgFoo2A 之前插入一个“标记”枚举值。
现在我们需要一种方法来确保 MARKER_1_DONT_USE
BASE2_VAL 在编译时。有两种常见的技术。
负大小的数组
声明具有负大小的数组是错误的。这看起来有点难看,但是很有效。
如果 MARKER_1_DONT_USE 大于 BASE_2_VAL,几乎所有编写过的编译器都会生成错误。 GCC 吐出:
静态断言
如果您的编译器支持 C11,则可以使用
_Static_assert
。对 C11 的支持并不普遍,但您的编译器无论如何都可能支持_Static_assert
,特别是因为 C++ 中的相应功能得到了广泛支持。GCC 吐出以下消息:
There are a couple ways to check this compile time, but they might not always work for you. Start by inserting a "marker" enum value right before MsgFoo2A.
Now we need a way to ensure that
MARKER_1_DONT_USE < BASE2_VAL
at compile-time. There are two common techiques.Negative size arrays
It is an error to declare an array with negative size. This looks a little ugly, but it works.
Almost every compiler ever written will generate an error if MARKER_1_DONT_USE is greater than BASE_2_VAL. GCC spits out:
Static assertions
If your compiler supports C11, you can use
_Static_assert
. Support for C11 is not ubiquitous, but your compiler may support_Static_assert
anyway, especially since the corresponding feature in C++ is widely supported.GCC spits out the following message:
我没有在您的要求中看到“漂亮”,因此我提交了使用 Boost 预处理器库实现的解决方案。
作为预先的免责声明,我没有大量使用 Boost.Preprocessor,我只使用此处提供的测试用例对其进行了测试,因此可能存在错误,并且可能有一种更简单、更干净的方法来执行此操作。我当然欢迎评论、更正、建议、侮辱等。
我们开始吧:
然后我们可以像这样使用它:
枚举值是可选的;此代码生成的枚举等效于:
它还生成一个健全性检查函数,其中包含 switch 语句,如 Ben Voigt 的回答。如果我们更改枚举声明,使枚举值不唯一,例如,
它将无法编译(Visual C++ 报告预期的错误 C2196:大小写值“1”已使用)。
还要感谢 Matthieu M.,他对另一个问题的回答让我对 Boost 预处理器库产生了兴趣。
I didn't see "pretty" in your requirements, so I submit this solution implemented using the Boost Preprocessor library.
As an up-front disclaimer, I haven't used Boost.Preprocessor a whole lot and I've only tested this with the test cases presented here, so there could be bugs, and there may be an easier, cleaner way to do this. I certainly welcome comments, corrections, suggestions, insults, etc.
Here we go:
We can then use it like so:
The enumerator value is optional; this code generates an enumeration equivalent to:
It also generates a sanity-check function that contains a switch statement as described in Ben Voigt's answer. If we change the enumeration declaration such that we have non-unique enumerator values, e.g.,
it will not compile (Visual C++ reports the expected error C2196: case value '1' already used).
Thanks also to Matthieu M., whose answer to another question got me interested in the Boost Preprocessor library.
我不认为有一种方法可以用语言本身来检测这一点,考虑到在可能的情况下您希望两个枚举值相同。但是,您始终可以确保所有显式设置的项目都位于列表的顶部:
只要分配的值位于顶部,就不可能发生冲突,除非由于某种原因宏扩展为相同的值。
通常,通过为每个 MsgFooX 组提供固定数量的位数并确保每个组不会溢出其分配的位数来克服此问题。 “位数”解决方案很好,因为它允许按位测试来确定某些内容属于哪个消息组。但是没有内置的语言功能可以做到这一点,因为枚举具有两个相同值的合法情况:
I don't believe there's a way to detect this with the language itself, considering there are conceivable cases where you'd want two enumeration values to be the same. You can, however, always ensure all explicitly set items are at the top of the list:
So long as assigned values are at the top, no collision is possible, unless for some reason the macros expand to values which are the same.
Usually this problem is overcome by giving a fixed number of bits for each MsgFooX group, and ensuring each group does not overflow it's allotted number of bits. The "Number of bits" solution is nice because it allows a bitwise test to determine to which message group something belongs. But there's no built-in language feature to do this because there are legitimate cases for an enum having two of the same value: