我应该使用 #define、enum 还是 const?
在我正在处理的 C++ 项目中,我有一个 flag 类型的值,它可以有四个值。 这四个标志可以组合起来。 标志描述数据库中的记录,可以是:
- 新记录
- 已删除记录
- 已修改记录
- 现有记录
现在,对于每条记录,我希望保留此属性,因此我可以使用枚举:
enum { xNew, xDeleted, xModified, xExisting }
但是,在代码中的其他位置,我需要选择哪个记录对用户可见,所以我希望能够将其作为单个参数传递,例如:
showRecords(xNew | xDeleted);
所以,看来我有三种可能的appoaches:
#define X_NEW 0x01
#define X_DELETED 0x02
#define X_MODIFIED 0x04
#define X_EXISTING 0x08
或
typedef enum { xNew = 1, xDeleted, xModified = 4, xExisting = 8 } RecordType;
或
namespace RecordType {
static const uint8 xNew = 1;
static const uint8 xDeleted = 2;
static const uint8 xModified = 4;
static const uint8 xExisting = 8;
}
空间要求很重要(字节与整数),但不重要至关重要的。 使用定义,我会失去类型安全性,而使用 enum
,我会失去一些空间(整数),并且当我想要进行按位操作时,可能必须进行强制转换。 使用 const 我认为我也会失去类型安全性,因为随机的 uint8 可能会错误地进入。
还有其他更清洁的方法吗?
如果没有,您会使用什么以及为什么?
PS 其余的代码是相当干净的现代 C++,没有 #define
,而且我在少数空间中使用了命名空间和模板,所以这些也不是没有问题的。
In a C++ project I'm working on, I have a flag kind of value which can have four values. Those four flags can be combined. Flags describe the records in database and can be:
- new record
- deleted record
- modified record
- existing record
Now, for each record I wish to keep this attribute, so I could use an enum:
enum { xNew, xDeleted, xModified, xExisting }
However, in other places in code, I need to select which records are to be visible to the user, so I'd like to be able to pass that as a single parameter, like:
showRecords(xNew | xDeleted);
So, it seems I have three possible appoaches:
#define X_NEW 0x01
#define X_DELETED 0x02
#define X_MODIFIED 0x04
#define X_EXISTING 0x08
or
typedef enum { xNew = 1, xDeleted, xModified = 4, xExisting = 8 } RecordType;
or
namespace RecordType {
static const uint8 xNew = 1;
static const uint8 xDeleted = 2;
static const uint8 xModified = 4;
static const uint8 xExisting = 8;
}
Space requirements are important (byte vs int) but not crucial. With defines I lose type safety, and with enum
I lose some space (integers) and probably have to cast when I want to do a bitwise operation. With const
I think I also lose type safety since a random uint8
could get in by mistake.
Is there some other cleaner way?
If not, what would you use and why?
P.S. The rest of the code is rather clean modern C++ without #define
s, and I have used namespaces and templates in few spaces, so those aren't out of question either.
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(15)
组合策略以减少单一方法的缺点。 我在嵌入式系统中工作,因此以下解决方案基于以下事实:整数和按位运算符速度快、内存低且安全。 闪存使用率低。
将枚举放在命名空间中,以防止常量污染全局命名空间。
枚举声明并定义编译时检查类型。 始终使用编译时类型检查来确保为参数和变量指定正确的类型。 C++ 中不需要 typedef。
为无效状态创建另一个成员。 这可以用作错误代码; 例如,当您想要返回状态但 I/O 操作失败时。 它对于调试也很有用; 在初始化列表和析构函数中使用它来知道是否应该使用变量的值。
考虑到您使用此类型有两个目的。 跟踪记录的当前状态并创建掩码以选择处于某些状态的记录。 创建一个内联函数来测试该类型的值是否对您的目的有效; 作为状态标记与状态掩码。 这会捕获错误,因为
typedef
只是一个int
,并且像0xDEADBEEF
这样的值可能会通过未初始化或错误指向的变量出现在您的变量中。如果您想经常使用该类型,请添加
using
指令。值检查函数在断言中非常有用,可以在使用错误值时立即捕获它们。 跑步时你越快发现错误,它造成的损害就越小。
以下是一些将所有内容放在一起的示例。
确保正确值安全的唯一方法是使用带有运算符重载的专用类,这留给其他读者作为练习。
Combine the strategies to reduce the disadvantages of a single approach. I work in embedded systems so the following solution is based on the fact that integer and bitwise operators are fast, low memory & low in flash usage.
Place the enum in a namespace to prevent the constants from polluting the global namespace.
An enum declares and defines a compile time checked typed. Always use compile time type checking to make sure arguments and variables are given the correct type. There is no need for the typedef in C++.
Create another member for an invalid state. This can be useful as error code; for example, when you want to return the state but the I/O operation fails. It is also useful for debugging; use it in initialisation lists and destructors to know if the variable's value should be used.
Consider that you have two purposes for this type. To track the current state of a record and to create a mask to select records in certain states. Create an inline function to test if the value of the type is valid for your purpose; as a state marker vs a state mask. This will catch bugs as the
typedef
is just anint
and a value such as0xDEADBEEF
may be in your variable through uninitialised or mispointed variables.Add a
using
directive if you want to use the type often.The value checking functions are useful in asserts to trap bad values as soon as they are used. The quicker you catch a bug when running, the less damage it can do.
Here are some examples to put it all together.
The only way to ensure correct value safety is to use a dedicated class with operator overloads and that is left as an exercise for another reader.
忘记定义
它们会污染你的代码。
位域?
永远不要使用它。 您更关心速度而不是节省 4 个整数。 使用位字段实际上比访问任何其他类型都要慢。
来源: http://en .wikipedia.org/wiki/Bit_field:
如果您需要更多不使用位域的理由,也许Raymond Chen 将在他的旧的新事物帖子中说服您:布尔值集合的位字段的成本效益分析,位于 http://blogs.msdn.com/oldnewthing/archive/2008/11/26/9143050.aspx
const int?
将它们放在命名空间中很酷。 如果它们在您的 CPP 或头文件中声明,它们的值将被内联。 您将能够对这些值使用 switch,但它会稍微增加耦合。
啊,是的:删除静态关键字。 当像您一样使用时, static 在 C++ 中已被弃用,并且如果 uint8 是内置类型,则不需要在同一模块的多个源包含的标头中声明它。 最后,代码应该是:
这种方法的问题是您的代码知道常量的值,这会稍微增加耦合。
enum
与 const int 相同,但类型稍强。
但它们仍然在污染全局命名空间。
顺便说一句...删除 typedef。 您正在使用 C++ 工作。 那些枚举和结构的 typedef 对代码的污染比其他任何东西都多。
结果有点:
如您所见,您的枚举正在污染全局名称空间。
如果你把这个枚举放在一个命名空间中,你会得到类似的东西:
extern const int ?
如果您想减少耦合(即能够隐藏常量的值,因此可以根据需要修改它们而无需完全重新编译),您可以在标头中将 int 声明为 extern,并在 CPP 文件中将其声明为常量,如下例所示:
并且:
不过,您将无法对这些常量使用 switch。 所以最后,选择你的毒药......
:-p
Forget the defines
They will pollute your code.
bitfields?
Don't ever use that. You are more concerned with speed than with economizing 4 ints. Using bit fields is actually slower than access to any other type.
Source: http://en.wikipedia.org/wiki/Bit_field:
And if you need more reasons to not use bitfields, perhaps Raymond Chen will convince you in his The Old New Thing Post: The cost-benefit analysis of bitfields for a collection of booleans at http://blogs.msdn.com/oldnewthing/archive/2008/11/26/9143050.aspx
const int?
Putting them in a namespace is cool. If they are declared in your CPP or header file, their values will be inlined. You'll be able to use switch on those values, but it will slightly increase coupling.
Ah, yes: remove the static keyword. static is deprecated in C++ when used as you do, and if uint8 is a buildin type, you won't need this to declare this in an header included by multiple sources of the same module. In the end, the code should be:
The problem of this approach is that your code knows the value of your constants, which increases slightly the coupling.
enum
The same as const int, with a somewhat stronger typing.
They are still polluting the global namespace, though.
By the way... Remove the typedef. You're working in C++. Those typedefs of enums and structs are polluting the code more than anything else.
The result is kinda:
As you see, your enum is polluting the global namespace.
If you put this enum in an namespace, you'll have something like:
extern const int ?
If you want to decrease coupling (i.e. being able to hide the values of the constants, and so, modify them as desired without needing a full recompilation), you can declare the ints as extern in the header, and as constant in the CPP file, as in the following example:
And:
You won't be able to use switch on those constants, though. So in the end, pick your poison...
:-p
您排除了 std::bitset 吗? 标志集就是它的用途。 做
然后
因为位集有一堆运算符重载,所以你现在可以做
或者类似的事情 - 我很感激任何更正,因为我还没有测试过这个。 您还可以按索引引用位,但通常最好只定义一组常量,并且 RecordType 常量可能更有用。
假设您已经排除了 bitset,我投票支持 enum。
我不认为强制转换枚举是一个严重的缺点 - 好吧,所以它有点吵,并且为枚举分配超出范围的值是未定义的行为,所以理论上有可能在一些不寻常的 C++ 上搬起石头砸自己的脚实施。 但是,如果您只在必要时才这样做(即从 int 到 enum iirc 时),那么这就是人们以前见过的完全正常的代码。
我也对枚举的空间成本表示怀疑。 uint8 变量和参数可能不会比 int 使用更少的堆栈,因此只有类中的存储很重要。 在某些情况下,将多个字节打包到一个结构中会获胜(在这种情况下,您可以将枚举传入和传出 uint8 存储),但通常填充无论如何都会消除这种好处。
因此,与其他枚举相比,枚举没有缺点,并且优点是为您提供了一些类型安全性(如果没有显式转换,则无法分配一些随机整数值)和引用所有内容的干净方式。
顺便说一下,为了方便起见,我还将“= 2”放入枚举中。 这不是必要的,但“最小惊讶原则”表明所有 4 个定义应该看起来相同。
Have you ruled out std::bitset? Sets of flags is what it's for. Do
then
Because there are a bunch of operator overloads for bitset, you can now do
Or something very similar to that - I'd appreciate any corrections since I haven't tested this. You can also refer to the bits by index, but it's generally best to define only one set of constants, and RecordType constants are probably more useful.
Assuming you have ruled out bitset, I vote for the enum.
I don't buy that casting the enums is a serious disadvantage - OK so it's a bit noisy, and assigning an out-of-range value to an enum is undefined behaviour so it's theoretically possible to shoot yourself in the foot on some unusual C++ implementations. But if you only do it when necessary (which is when going from int to enum iirc), it's perfectly normal code that people have seen before.
I'm dubious about any space cost of the enum, too. uint8 variables and parameters probably won't use any less stack than ints, so only storage in classes matters. There are some cases where packing multiple bytes in a struct will win (in which case you can cast enums in and out of uint8 storage), but normally padding will kill the benefit anyhow.
So the enum has no disadvantages compared with the others, and as an advantage gives you a bit of type-safety (you can't assign some random integer value without explicitly casting) and clean ways of referring to everything.
For preference I'd also put the "= 2" in the enum, by the way. It's not necessary, but a "principle of least astonishment" suggests that all 4 definitions should look the same.
以下是关于 const、宏、枚举的几篇文章:
符号常量
枚举常量与常量对象
我认为你应该避免使用宏,特别是因为你的大部分新代码都是用现代 C++ 编写的。
Here are couple of articles on const vs. macros vs. enums:
Symbolic Constants
Enumeration Constants vs. Constant Objects
I think you should avoid macros especially since you wrote most of your new code is in modern C++.
如果可能,不要使用宏。 当谈到现代 C++ 时,他们并没有受到太多的钦佩。
If possible do NOT use macros. They aren't too much admired when it comes to modern C++.
不一定......
不一定 - 但你必须在存储点明确......
您可以创建运算符来消除这种痛苦:
任何这些机制都可能发生同样的情况:范围和值检查通常与类型安全正交(尽管用户定义的类型 - 即您自己的类 - 可以强制执行关于其数据的“不变量”)。 使用枚举,编译器可以自由选择更大的类型来托管值,并且未初始化、损坏或只是错误设置的枚举变量仍然可能最终将其位模式解释为您不期望的数字 - 与任何不相等的数字进行比较枚举标识符、它们的任意组合和 0。
好吧,最终,一旦您在图片中拥有位字段和自定义运算符,经过尝试且值得信赖的 C 风格按位或枚举就可以很好地工作。 您可以使用一些自定义验证函数和断言进一步提高鲁棒性,如 mat_geek 的答案; 技术通常同样适用于处理字符串、整数、双精度值等。
您可能会说这是“更干净”:
我无所谓:数据位打包更紧密,但代码显着增长...取决于您有多少个对象得到了,而 lamdbas——尽管它们很漂亮——仍然比按位或运算更混乱、更难得到正确结果。
顺便说一句,关于线程安全性的争论相当薄弱,恕我直言,最好将其作为背景考虑因素,而不是成为主导决策驱动力; 即使不知道它们的打包,跨位域共享互斥体也是一种更可能的做法(互斥体是相对庞大的数据成员 - 我必须真正关心性能才能考虑在一个对象的成员上使用多个互斥体,并且我会仔细查看足以注意到它们是位字段)。 任何子字大小类型都可能有相同的问题(例如
uint8_t
)。 无论如何,如果您迫切需要更高的并发性,您可以尝试原子比较和交换样式的操作。Not necessarily...
Not necessarily - but you do have to be explicit at points of storage...
You can create operators to take the pain out of that:
The same can happen with any of these mechanisms: range and value checks are normally orthogonal to type safety (though user-defined-types - i.e. your own classes - can enforce "invariants" about their data). With enums, the compiler's free to pick a larger type to host the values, and an uninitialised, corrupted or just miss-set enum variable could still end up interpretting its bit pattern as a number you wouldn't expect - comparing unequal to any of the enumeration identifiers, any combination of them, and 0.
Well, in the end the tried-and-trusted C-style bitwise OR of enumerations works pretty well once you have bit fields and custom operators in the picture. You can further improve your robustness with some custom validation functions and assertions as in mat_geek's answer; techniques often equally applicable to handling string, int, double values etc..
You could argue that this is "cleaner":
I'm indifferent: the data bits pack tighter but the code grows significantly... depends how many objects you've got, and the lamdbas - beautiful as they are - are still messier and harder to get right than bitwise ORs.
BTW /- the argument about thread safety's pretty weak IMHO - best remembered as a background consideration rather than becoming a dominant decision-driving force; sharing a mutex across the bitfields is a more likely practice even if unaware of their packing (mutexes are relatively bulky data members - I have to be really concerned about performance to consider having multiple mutexes on members of one object, and I'd look carefully enough to notice they were bit fields). Any sub-word-size type could have the same problem (e.g. a
uint8_t
). Anyway, you could try atomic compare-and-swap style operations if you're desperate for higher concurrency.枚举会更合适,因为它们提供“标识符的含义”以及类型安全。 即使多年后,您也可以清楚地看出“xDeleted”属于“RecordType”并且代表“记录类型”(哇!)。 常量需要对此进行注释,而且它们还需要在代码中上下移动。
Enums would be more appropriate as they provide "meaning to the identifiers" as well as type safety. You can clearly tell "xDeleted" is of "RecordType" and that represent "type of a record" (wow!) even after years. Consts would require comments for that, also they would require going up and down in code.
即使您必须使用 4 个字节来存储枚举(我对 C++ 不太熟悉——我知道您可以在 C# 中指定底层类型),它仍然是值得的——使用枚举。
在当今服务器拥有 GB 内存的时代,应用程序级别的 4 字节内存与 1 字节内存之类的问题通常并不重要。 当然,如果在您的特定情况下,内存使用非常重要(并且您无法让 C++ 使用字节来支持枚举),那么您可以考虑“static const”路线。
归根结底,您必须问自己,使用“static const”来为数据结构节省 3 个字节的内存是否值得进行维护?
还有一点要记住——IIRC,在 x86 上,数据结构是 4 字节对齐的,因此除非“记录”结构中有许多字节宽度元素,否则实际上可能并不重要。 在对性能/空间的可维护性进行权衡之前,进行测试并确保其有效。
Even if you have to use 4 byte to store an enum (I'm not that familiar with C++ -- I know you can specify the underlying type in C#), it's still worth it -- use enums.
In this day and age of servers with GBs of memory, things like 4 bytes vs. 1 byte of memory at the application level in general don't matter. Of course, if in your particular situation, memory usage is that important (and you can't get C++ to use a byte to back the enum), then you can consider the 'static const' route.
At the end of the day, you have to ask yourself, is it worth the maintenance hit of using 'static const' for the 3 bytes of memory savings for your data structure?
Something else to keep in mind -- IIRC, on x86, data structures are 4-byte aligned, so unless you have a number of byte-width elements in your 'record' structure, it might not actually matter. Test and make sure it does before you make a tradeoff in maintainability for performance/space.
如果您想要类的类型安全性,以及枚举语法和位检查的便利性,请考虑 安全标签在 C++ 中。 我和作者合作过,他很聪明。
不过要小心。 最后,这个包使用了模板和宏!
If you want the type safety of classes, with the convenience of enumeration syntax and bit checking, consider Safe Labels in C++. I've worked with the author, and he's pretty smart.
Beware, though. In the end, this package uses templates and macros!
您实际上是否需要将标志值作为一个概念整体传递,或者您是否会有很多每个标志的代码? 无论哪种方式,我认为将其作为 1 位位域的类或结构实际上可能更清晰:
然后您的记录类可以有一个 struct RecordFlag 成员变量,函数可以采用 struct RecordFlag 类型的参数等。编译器应该打包位域在一起,节省空间。
Do you actually need to pass around the flag values as a conceptual whole, or are you going to have a lot of per-flag code? Either way, I think having this as class or struct of 1-bit bitfields might actually be clearer:
Then your record class could have a struct RecordFlag member variable, functions can take arguments of type struct RecordFlag, etc. The compiler should pack the bitfields together, saving space.
我可能不会将枚举用于这种可以将值组合在一起的事情,更典型的是,枚举是互斥的状态。
但无论您使用哪种方法,为了更清楚地表明这些值是可以组合在一起的位,请对实际值使用以下语法:
使用左移有助于表明每个值都是一个单个位,以后有人做错事(例如添加新值并为其分配值 9)的可能性较小。
I probably wouldn't use an enum for this kind of a thing where the values can be combined together, more typically enums are mutually exclusive states.
But whichever method you use, to make it more clear that these are values which are bits which can be combined together, use this syntax for the actual values instead:
Using a left-shift there helps to indicate that each value is intended to be a single bit, it is less likely that later on someone would do something wrong like add a new value and assign it something a value of 9.
基于KISS,高内聚和低耦合,问这些问题 -
有一本很棒的书“大规模C++软件设计”,这本书促进了外部基本类型,如果您可以避免其他头文件/接口依赖项,您应该尝试这样做。
Based on KISS, high cohesion and low coupling, ask these questions -
There is a great book "Large-Scale C++ Software Design", this promotes base types externally, if you can avoid another header file/interface dependancy you should try to.
如果您使用 Qt,您应该查看 QFlags。
QFlags 类提供了一种类型安全的方式来存储枚举值的 OR 组合。
If you are using Qt you should have a look for QFlags.
The QFlags class provides a type-safe way of storing OR-combinations of enum values.
我宁愿选择
简单的原因:
I would rather go with
Simply because:
并不是说我喜欢过度设计一切,但有时在这些情况下,可能值得创建一个(小)类来封装这些信息。
如果您创建一个 RecordType 类,那么它可能具有如下函数:
void setDeleted();
无效清除删除();
布尔 isDeleted();
等等...(或任何约定)
它可以验证组合(在并非所有组合都是合法的情况下,例如,如果“新”和“删除”不能同时设置)。 如果您只使用位掩码等,那么设置状态的代码需要验证,类也可以封装该逻辑。
该类还可以使您能够将有意义的日志记录信息附加到每个状态,您可以添加一个函数来返回当前状态等的字符串表示形式(或使用流运算符“<<”)。
尽管如此,如果您担心存储,您仍然可以让该类只有一个“char”数据成员,因此只需占用少量存储(假设它是非虚拟的)。 当然,根据硬件等,您可能会遇到对齐问题。
如果实际的位值位于 cpp 文件内的匿名命名空间中而不是位于头文件中,则可能对“世界”的其他部分不可见。
如果您发现使用 enum/#define/ 位掩码等的代码有很多“支持”代码来处理无效组合、日志记录等,那么封装在类中可能值得考虑。 当然,大多数时候简单的问题用简单的解决方案会更好......
Not that I like to over-engineer everything but sometimes in these cases it may be worth creating a (small) class to encapsulate this information.
If you create a class RecordType then it might have functions like:
void setDeleted();
void clearDeleted();
bool isDeleted();
etc... (or whatever convention suits)
It could validate combinations (in the case where not all combinations are legal, eg if 'new' and 'deleted' could not both be set at the same time). If you just used bit masks etc then the code that sets the state needs to validate, a class can encapsulate that logic too.
The class may also give you the ability to attach meaningful logging info to each state, you could add a function to return a string representation of the current state etc (or use the streaming operators '<<').
For all that if you are worried about storage you could still have the class only have a 'char' data member, so only take a small amount of storage (assuming it is non virtual). Of course depending on the hardware etc you may have alignment issues.
You could have the actual bit values not visible to the rest of the 'world' if they are in an anonymous namespace inside the cpp file rather than in the header file.
If you find that the code using the enum/#define/ bitmask etc has a lot of 'support' code to deal with invalid combinations, logging etc then encapsulation in a class may be worth considering. Of course most times simple problems are better off with simple solutions...