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

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

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:

  • 新记录
  • 删除记录
  • 修改记录
  • 现有记录

现在,对于我希望保留此属性的每条记录,我可以使用枚举:

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

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;
}

空间要求很重要(byte vs int)但不是关键.使用定义我失去了类型安全性,而使用 enum 我失去了一些空间(整数)并且当我想要进行按位运算时可能不得不强制转换.使用 const 我想我也失去了类型安全性,因为随机的 uint8 可能会错误地进入.

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?

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

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.

推荐答案

组合策略以减少单一方法的缺点.我在嵌入式系统中工作,因此以下解决方案基于整数和按位运算符快速、低内存和低内存的事实.闪存使用率低.

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 {

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

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

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

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

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.

xInvalid = 16 };

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

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 an int and a value such as 0xDEADBEEF may be in your variable through uninitialised or mispointed variables.

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 ;
}

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

Add a using directive if you want to use the type often.

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.

相关文章