C++ 我应该使用#define、enum 还是const?

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/112433/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-27 12:56:13  来源:igfitidea点击:

Should I use #define, enum or const?

c++enumsbit-manipulationc-preprocessor

提问by Milan Babu?kov

In a C++ project I'm working on, I have a flagkind of value which can have four values. Those four flags can be combined. Flags describe the records in database and can be:

在我正在处理的 C++ 项目中,我有一个标志类型的值,它可以有四个值。这四个标志可以组合在一起。标志描述数据库中的记录,可以是:

  • 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 enumI lose some space (integers) and probably have to cast when I want to do a bitwise operation. With constI think I also lose type safety since a random uint8could get in by mistake.

空间要求很重要(字节与整数),但并不重要。使用定义我失去了类型安全性,并且enum我失去了一些空间(整数)并且在我想要进行按位运算时可能必须进行强制转换。随着const我想我也失去了类型安全,因为随机的uint8可能错误地进入。

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 #defines, and I have used namespaces and templates in few spaces, so those aren't out of question either.

PS 其余的代码是相当干净的现代 C++,没有#defines,我在几个空格中使用了命名空间和模板,所以这些也不是没有问题。

采纳答案by mat_geek

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.

将枚举放在命名空间中,以防止常量污染全局命名空间。

namespace RecordType {

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++.

枚举声明并定义了编译时检查类型。始终使用编译时类型检查来确保参数和变量的类型正确。C++ 中不需要 typedef。

enum TRecordType { xNew = 1, xDeleted = 2, xModified = 4, xExisting = 8,

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.

为无效状态创建另一个成员。这可以用作错误代码;例如,当您想返回状态但 I/O 操作失败时。它也可用于调试;在初始化列表和析构函数中使用它来知道是否应该使用变量的值。

xInvalid = 16 };

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 typedefis just an intand a value such as 0xDEADBEEFmay be in your variable through uninitialised or mispointed variables.

考虑到这种类型有两个目的。跟踪记录的当前状态并创建掩码以选择处于某些状态的记录。创建一个内联函数来测试该类型的值是否符合您的目的;作为状态标记与状态掩码。这将捕获错误,因为typedef它只是一个int和一个值,例如0xDEADBEEF可能通过未初始化或错误指向的变量在您的变量中。

inline bool IsValidState( TRecordType v) {
    switch(v) { case xNew: case xDeleted: case xModified: case xExisting: return true; }
    return false;
}

 inline bool IsValidMask( TRecordType v) {
    return v >= xNew  && v < xInvalid ;
}

Add a usingdirective if you want to use the type often.

using如果您想经常使用该类型,请添加一个指令。

using RecordType ::TRecordType ;

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.

这里有一些例子可以把它们放在一起。

void showRecords(TRecordType mask) {
    assert(RecordType::IsValidMask(mask));
    // do stuff;
}

void wombleRecord(TRecord rec, TRecordType state) {
    assert(RecordType::IsValidState(state));
    if (RecordType ::xNew) {
    // ...
} in runtime

TRecordType updateRecord(TRecord rec, TRecordType newstate) {
    assert(RecordType::IsValidState(newstate));
    //...
    if (! access_was_successful) return RecordType ::xInvalid;
    return newstate;
}

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.

确保正确值安全的唯一方法是使用带有运算符重载的专用类,这留给其他读者作为练习。

回答by paercebal

Forget the defines

忘记定义

They will pollute your code.

他们会污染你的代码。

bitfields?

位域?

struct RecordFlag {
    unsigned isnew:1, isdeleted:1, ismodified:1, isexisting:1;
};

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.

永远不要使用那个。与节省 4 个整数相比,您更关心速度。使用位域实际上比访问任何其他类型都慢。

However, bit members in structs have practical drawbacks. First, the ordering of bits in memory varies from compiler to compiler. In addition, many popular compilers generate inefficient code for reading and writing bit members, and there are potentially severe thread safety issuesrelating to bit fields (especially on multiprocessor systems) due to the fact that most machines cannot manipulate arbitrary sets of bits in memory, but must instead load and store whole words. e.g the following would not be thread-safe, in spite of the use of a mutex
然而,结构中的位成员有实际的缺点。首先,内存中位的排序因编译器而异。此外,许多流行的编译器生成读取和写入位成员的低效代码,并且由于大多数机器无法操作内存中的任意位集,因此存在与位域相关的潜在严重线程安全问题(尤其是在多处理器系统上),但必须加载和存储整个单词。例如,尽管使用了互斥锁,但以下内容不是线程安全的

Source: http://en.wikipedia.org/wiki/Bit_field:

来源:http: //en.wikipedia.org/wiki/Bit_field

And if you need more reasons to notuse bitfields, perhaps Raymond Chenwill convince you in his The Old New ThingPost: The cost-benefit analysis of bitfields for a collection of booleansat http://blogs.msdn.com/oldnewthing/archive/2008/11/26/9143050.aspx

如果您需要更多使用位域的理由,也许Raymond Chen会在他的The Old New ThingPost: The cost-enity analysis of bitfields for a collection of booleans中说服您,网址http://blogs.msdn.com/oldnewthing/存档/2008/11/26/9143050.aspx

const int?

常量整数?

namespace RecordType {
    static const uint8 xNew = 1;
    static const uint8 xDeleted = 2;
    static const uint8 xModified = 4;
    static const uint8 xExisting = 8;
}

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.

将它们放在命名空间中很酷。如果它们在您的 CPP 或头文件中声明,它们的值将被内联。您将能够在这些值上使用 switch,但它会略微增加耦合。

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:

啊,是的:删除 static 关键字。像您一样使用时,C++ 中不推荐使用 static ,并且如果 uint8 是内置类型,则您不需要它来在同一模块的多个源包含的标头中声明它。最后,代码应该是:

namespace RecordType {
    const uint8 xNew = 1;
    const uint8 xDeleted = 2;
    const uint8 xModified = 4;
    const uint8 xExisting = 8;
}

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.

与 const int 相同,具有更强的类型。

typedef enum { xNew = 1, xDeleted, xModified = 4, xExisting = 8 } RecordType;

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.

不过,它们仍在污染全局命名空间。顺便说一句......删除 typedef。您正在使用 C++。那些枚举和结构的 typedef 比其他任何东西都更能污染代码。

The result is kinda:

结果有点:

enum RecordType { xNew = 1, xDeleted, xModified = 4, xExisting = 8 } ;

void doSomething(RecordType p_eMyEnum)
{
   if(p_eMyEnum == xNew)
   {
       // etc.
   }
}

As you see, your enum is polluting the global namespace. If you put this enum in an namespace, you'll have something like:

如您所见,您的枚举正在污染全局命名空间。如果你把这个枚举放在一个命名空间中,你会得到类似的东西:

namespace RecordType {
   enum Value { xNew = 1, xDeleted, xModified = 4, xExisting = 8 } ;
}

void doSomething(RecordType::Value p_eMyEnum)
{
   if(p_eMyEnum == RecordType::xNew)
   {
       // etc.
   }
}

extern const int ?

外部常量 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:

如果您想减少耦合(即能够隐藏常量的值,因此可以根据需要修改它们而无需完全重新编译),您可以在头文件中将整数声明为 extern,并在 CPP 文件中声明为常量,如下例所示:

// Header.hpp
namespace RecordType {
    extern const uint8 xNew ;
    extern const uint8 xDeleted ;
    extern const uint8 xModified ;
    extern const uint8 xExisting ;
}

And:

和:

// Source.hpp
namespace RecordType {
    const uint8 xNew = 1;
    const uint8 xDeleted = 2;
    const uint8 xModified = 4;
    const uint8 xExisting = 8;
}

You won't be able to use switch on those constants, though. So in the end, pick your poison... :-p

但是,您将无法在这些常量上使用 switch。所以最后,选择你的毒药...... :-p

回答by Steve Jessop

Have you ruled out std::bitset? Sets of flags is what it's for. Do

你排除了 std::bitset 吗?标志集就是它的用途。做

typedef std::bitset<4> RecordType;

then

然后

static const RecordType xNew(1);
static const RecordType xDeleted(2);
static const RecordType xModified(4);
static const RecordType xExisting(8);

Because there are a bunch of operator overloads for bitset, you can now do

因为 bitset 有一堆操作符重载,你现在可以做

RecordType rt = whatever;      // unsigned long or RecordType expression
rt |= xNew;                    // set 
rt &= ~xDeleted;               // clear 
if ((rt & xModified) != 0) ... // test

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.

或者与此非常相似的东西 - 我很感激任何更正,因为我还没有测试过这个。您也可以通过索引来引用位,但通常最好只定义一组常量,而 RecordType 常量可能更有用。

Assuming you have ruled out bitset, I vote for the enum.

假设你已经排除了 bitset,我投票给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.

我不认为转换枚举是一个严重的缺点 - 好吧,所以它有点嘈杂,并且将超出范围的值分配给枚举是未定义的行为,因此理论上有可能在一些不寻常的 C++ 上让自己陷入困境实现。但是如果你只在必要时才这样做(从 int 到 enum iirc),这是人们以前见过的完全正常的代码。

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.

我也怀疑枚举的任何空间成本。uint8 变量和参数使用的堆栈可能不会比 int 少,因此只有类中的存储才重要。在某些情况下,在结构中打包多个字节会获胜(在这种情况下,您可以在 uint8 存储中输入和输出枚举),但通常填充无论如何都会扼杀好处。

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.

顺便说一下,作为偏好,我还将“= 2”放在枚举中。这不是必需的,但“最小惊讶原则”表明所有 4 个定义应该看起来相同。

回答by Abbas

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++ 编写的。

回答by INS

If possible do NOT use macros. They aren't too much admired when it comes to modern C++.

如果可能,不要使用宏。当涉及到现代 C++ 时,它们并不太受人尊敬。

回答by Tony Delroy

With defines I lose type safety

使用定义我失去了类型安全

Not necessarily...

不必要...

// signed defines
#define X_NEW      0x01u
#define X_NEW      (unsigned(0x01))  // if you find this more readable...

and with enum I lose some space (integers)

和枚举我失去了一些空间(整数)

Not necessarily - but you do have to be explicit at points of storage...

不一定 - 但你必须在存储点明确......

struct X
{
    RecordType recordType : 4;  // use exactly 4 bits...
    RecordType recordType2 : 4;  // use another 4 bits, typically in the same byte
    // of course, the overall record size may still be padded...
};

and probably have to cast when I want to do bitwise operation.

并且当我想进行按位运算时可能必须进行转换。

You can create operators to take the pain out of that:

您可以创建操作符来解决这个问题:

RecordType operator|(RecordType lhs, RecordType rhs)
{
    return RecordType((unsigned)lhs | (unsigned)rhs);
}

With const I think I also lose type safety since a random uint8 could get in by mistake.

使用 const 我想我也失去了类型安全性,因为随机 uint8 可能会错误地进入。

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.

这些机制中的任何一个都可能发生同样的情况:范围和值检查通常与类型安全正交(尽管用户定义的类型 - 即您自己的类 - 可以强制执行有关其数据的“不变量”)。使用枚举,编译器可以自由选择更大的类型来承载值,并且未初始化、损坏或只是未设置的枚举变量最终仍可能将其位模式解释为您不期望的数字 - 比较不等于任何一个枚举标识符、它们的任意组合和 0。

Is there some other cleaner way? / If not, what would you use and why?

还有其他更清洁的方法吗?/ 如果不是,你会用什么,为什么?

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..

好吧,最后,一旦您在图片中有位字段和自定义运算符,那么久经考验的 C 风格的枚举按位 OR 就可以很好地工作。您可以使用 mat_geek 的答案中的一些自定义验证函数和断言进一步提高您的健壮性;技术通常同样适用于处理字符串、整数、双精度值等。

You could argue that this is "cleaner":

您可能会争辩说这是“更干净”:

enum RecordType { New, Deleted, Modified, Existing };

showRecords([](RecordType r) { return r == New || r == Deleted; });

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.

我无动于衷:数据位打包得更紧,但代码显着增长......取决于你有多少对象,而 lamdbas - 尽管它们很漂亮 - 仍然比按位 OR 更混乱,更难得到正确的结果。

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.

顺便说一句 /- 恕我直言,关于线程安全性相当弱的论点 - 最好将其作为背景考虑而不是成为主要的决策驱动力;跨位域共享互斥锁是一种更有可能的做法,即使不知道它们的打包(互斥锁是相对庞大的数据成员 - 我必须真正关心性能才能考虑在一个对象的成员上使用多个互斥锁,我会仔细查看足以注意到它们是位字段)。任何子字大小类型都可能有相同的问题(例如 a uint8_t)。无论如何,如果您迫切需要更高的并发性,您可以尝试原子比较和交换风格的操作。

回答by hayalci

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.

枚举会更合适,因为它们提供“标识符的含义”以及类型安全。您可以清楚地告诉“xDeleted”是“RecordType”并且代表“记录类型”(哇!)即使在多年之后。Consts 需要对此进行注释,也需要在代码中上下移动。

回答by Jonathan Rupp

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.

即使您必须使用 4 字节来存储枚举(我对 C++ 不太熟悉——我知道您可以在 C# 中指定底层类型),它仍然值得——使用枚举。

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.

在当今拥有 GB 内存的服务器时代,应用程序级别的 4 字节与 1 字节内存之类的事情通常无关紧要。当然,如果在您的特定情况下,内存使用如此重要(并且您无法让 C++ 使用字节来支持枚举),那么您可以考虑“静态常量”路由。

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?

在一天结束时,您必须问自己,使用“静态常量”来为您的数据结构节省 3 个字节的内存是否值得维护?

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.

还有一点要记住——IIRC,在 x86 上,数据结构是 4 字节对齐的,所以除非你的“记录”结构中有许多字节宽度的元素,否则它实际上可能并不重要。在对性能/空间的可维护性进行权衡之前,测试并确保它可以实现。

回答by Don Wakefield

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.

如果您想要类的类型安全,以及枚举语法和位检查的便利,请考虑C++ 中的安全标签。我和作者一起工作过,他很聪明。

Beware, though. In the end, this package uses templates andmacros!

不过要小心。最后,这个包使用了模板宏!

回答by titanae

Based on KISS, high cohesion and low coupling, ask these questions -

基于KISS高内聚低耦合,问这些问题-

  • Who needs to know? my class, my library, other classes, other libraries, 3rd parties
  • What level of abstraction do I need to provide? Does the consumer understand bit operations.
  • Will I have have to interface from VB/C# etc?
  • 谁需要知道?我的班级,我的图书馆,其他班级,其他图书馆,第 3 方
  • 我需要提供什么级别的抽象?消费者是否了解位操作。
  • 我是否必须从 VB/C# 等接口?

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.

有一本很棒的书“大型 C++ 软件设计”,它从外部提升了基本类型,如果您可以避免另一个头文件/接口依赖性,您应该尝试。