我应该使用#定义,枚举或常量?

在C ++项目我的工作,我有一个标志一种价值,可以有四个值。 这四个标志可以结合起来。 标志描述在数据库中的记录,并且可以是:

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

现在,每个记录我要保持这种属性,所以我可以用一个枚举:

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

空间的要求是很重要的(字节VS INT),但不是关键。 随着定义我失去的类型安全,并与enum我失去了一些空间(整数),可能要投的时候我想做一个按位运算。 随着const我想我也失去类型安全因为随机uint8可以得到的错误。

有一些其他清洁的方式?

如果没有,你会用什么,为什么?

PS代码的其余部分是相当清洁的现代C ++没有#define s和我在几个空格使用的命名空间和模板,所以这些都是不出来的问题提出。

--------------解决方案-------------

合并战略,以减少单一方法的缺点。 我在嵌入式系统中工作,所以下面解决方案是基于这样的事实,整数和位运算符的速度快,低存储器低闪光灯的使用。

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

namespace RecordType {

枚举声明和定义一个编译时检查类型。 始终使用编译时类型检查,以确保参数和变量给出正确的类型。 有没有必要在C ++中的typedef。

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

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

xInvalid = 16 };

想想看,你有两个目的,这种类型的。 要跟踪记录的当前状态,并创建一个面具来选择某些国家的记录。 创建一个内联函数测试类型的值是否有效你的目的; 作为一个国家标志VS状态面具。 这将捉虫子的typedef只是一个int和值,如0xDEADBEEF可能会在您的变量,通过未初始化或mispointed变量。

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 ,如果你想使用的类型往往指令。

using RecordType ::TRecordType ;

该值检查功能是有用的断言捕获错误值,尽快为他们的使用。 运行时,你快赶上一个错误,它可以做少受损害。

下面是一些例子,把它放在一起。

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

以确保正确的值安全的唯一方法是使用一个专用的类运算符重载,并且被作为一个练习的另一位读者。

忘记定义

他们会污染你的代码。

位域?

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

千万不要使用 。 您是不是与节约4整数更关心的是速度。 使用位字段实际上是比获得任何其他类型的慢。

然而,在结构位成员有实际的缺点。 首先,位在内存中的排序,从编译器不同而不同编译器。 此外 ,许多流行的编译器生成低效的代码读取和写入位成员,也有由于这样的事实,大部分机器无法操作的任意位组在内存与位域潜在的严重的线程安全问题 (特别是在多处理器系统),而必须加载和存储整个单词。 例如,下面不会是线程安全的,尽管使用互斥的

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

如果你需要更多的理由不使用位域,也许雷蒙德陈将说服你在他的旧的新帖子: 位域的成本效益分析,布尔在 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;
}

把它们放在一个命名空间是很酷的。 如果他们在你的CPP或头文件中声明,其值将被内联。 你可以使用交换机上的这些值,但是它会略有增加耦合。

啊,对了去掉static关键字 。 静态不赞成在C ++中使用时,为你做的,如果UINT8是buildin类型,你不需要这个在包括同一模块的多个源的头部声明这一点。 在结束时,代码应该是:

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

这种方法的问题是,你的代码知道你的常量的值,这稍稍提高了耦合。

枚举

同为const int的,带着几分更强的类型。

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

他们仍然在污染全局命名空间,虽然。 顺便说一句...删除typedef的 。 你工作在C ++中。 枚举和结构的那些的typedef污染的代码比什么都重要。

其结果是有点儿:

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

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

正如你看到的,你枚举污染全局命名空间。 如果你把这个枚举在命名空间中,你必须是这样的:

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的?

如果你想减少耦合(也就是能够隐藏常量的值,因此,根据需要而无需完全重新编译修改它们),你可以声明整数为extern的头,并恒定在CPP文件,如在下面的例子:

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

和:

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

您将无法使用开关这些常数,虽然。 所以,最后,挑选你的毒药...:磷

你有没有排除的std :: bitset的? 标志的设置是它的。 做

typedef std::bitset<4> RecordType;

然后

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

因为有一帮运算符重载的位集合的,你现在可以做

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

或者一些非常相似的 - 我会很感激的任何修改,因为我没有测试过这一点。 你也可以参考位由指数,但一般最好以限定只有一组的常量,以及记录类型常数可能更有用。

假设你已经排除了位集合,我投给了枚举

我不买铸造枚举是一个严重的缺点 - 行,所以这是一个有点吵,和分配超出范围的值,以枚举是未定义行为,所以它是理论上可以搬起石头砸自己的脚了一些不寻常的C ++实现。 但是如果你只在必要时(这是从int打算什么时候枚举IIRC)做到这一点,这是完全的人都看到过正常的代码。

我半信半疑枚举任何空间成本了。 UINT8变量和参数可能不会使用任何堆栈不到整数,所以只能存储类的问题。 有些情况下,打包多个字节的结构将赢得(在这种情况下,你可以投枚举进出UINT8的存储设备),但通常填充将杀死的利益,无论如何。

因此,枚举没有缺点与其他人相比,并作为一个优势,给你一个位类型安全的(不能分配没有明确铸造一些随机整数值),并指一切清洁方式。

对于偏爱我还会把“= 2”中的枚举的方式。 这是没有必要的,但一个“最小惊讶原则”表明,所有4个定义应该是相同的。

这里有几个对常量与宏与枚举的文章:

符号常量
枚举常量与常量对象

我想你应该避免宏特别是因为你写了大部分的新代码是在现代C ++。

尽量不要使用宏。 当谈到现代C ++他们没有太多的羡慕。

为他们提供“意义的标识符”,以及类型安全枚举会更合适。 你可以清楚地告诉“xDeleted”是“记录类型”,并表示“式的记录”(哇!)虽然经过了多年。 Consts需要征求意见,同样它们需要上上下下在代码。

随着定义我失去的类型安全

不必要...

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

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

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

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

大概要投的时候我想做按位运算。

您可以创建经营者采取的痛苦出来的:

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

使用const我觉得我也失去类型安全因为随机UINT8可以得到的错误。

同样可以与任何这些机制发生:范围和值检查通常是正交的类型安全(尽管用户定义的类型 - 即自己的类 - 可以强制执行“不变量”他们的数据)。 随着枚举,编译器的自由选择一个更大的类型来承载的价值观,以及未初始化,损坏或只是错过设置枚举变量​​仍可能最终训释的位模式为数字,你不会指望 - 比较不平等的任何枚举标识符,其中,和0的任意组合。

有一些其他清洁的方式? /如果不是,你会使用,为什么呢?

那么,到底是可靠的,值得信赖的C风格的按位枚举或工作得很好,一旦你有位字段和运营商定制的图片。 您可以进一步改善与一些自定义的验证功能,并断言在mat_geek的回答你的鲁棒性; 技术往往同样适用于处理字符串,整型,双值等。

你可以说这是“干净”:

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

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

我淡然:数据位包更紧但代码显著的增长...取决于有多少个对象,你所得到的,而lamdbas - 美丽的,因为它们是 - 仍然是梅西耶,更难获得正确的不是按位OR值。

BTW / - 在有关线程安全的相当薄弱恕我直言说法 - 最好的怀念作为背景的​​考虑,而不是成为一个占主导地位的决策动力; 共享一个互斥体跨位域是即使不了解自己的包装(互斥量都比较庞大的数据成员更可能的做法 - 我必须非常关心性能考虑让在一个对象的成员多个互斥锁,而且我仔细看看足以看到他们是位字段)。 任何副字大小的类型可以有同样的问题(如uint8_t )。 无论如何,你可以尝试原子比较并交换式的操作,如果你渴望更高的并发。

即使你有使用4个字节来存储一个枚举(我没那么熟悉C ++ - 我知道你可以指定的基本类型在C#),它仍然是值得的 - 使用枚举。

在这一天,并与存储器绿带服务器年龄,诸如此类的4个字节与内存在一般应用级别1字节并不重要。 当然,如果你的特殊情况,内存使用情况是重要的(你不能让C ++使用一个字节来支持枚举),那么你可以考虑“静态常量”的路线。

在一天结束的时候,你要问自己,是值得使用“静态常量'为3个字节的内存为您节省数据结构的维护命中?

别的东西要记住 - IIRC,在x86,数据结构是4字节对齐,所以除非你有一些在你的'记录'结构字节宽度的元素,它可能不是真正重要。 测试并确保它确实你犯了一个折衷的可维护性性能/空间之前。

如果你想类的类型安全,以枚举的语法和位检查的便利性,考虑用C安全标签++。 我与作者的工作,他是很聪明的。

要小心,虽然。 最终,这个包使用模板和宏

你真的需要通过周围的标志值作为一个整体的概念,或者你将有一个很大的每个标志码? 无论哪种方式,我认为有这个作为类或1位域的结构实际上可能会更清楚:

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

那么你的记录类可以有一个结构RecordFlag成员变量,函数可以利用类型结构RecordFlag参数等编译器应该打包位域在一起,节省了空间。

我可能不会使用枚举这类的事情,其中​​的值可以被组合在一起,更典型的枚举是相互排斥的状态。

但是,无论您使用的方法,使之更加清楚,这些是位可一起组合值,使用该语法的实际值,而不是:

#define X_NEW (1 << 0)
#define X_DELETED (1 << 1)
#define X_MODIFIED (1 << 2)
#define X_EXISTING (1 << 3)

使用左移有帮助,表示每个值的目的是成为一个位,这是不太可能以后会有人做了错事一样添加一个新值,并分配给它的东西值9。

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

  • 谁需要知道吗? 我的课,我的图书馆,其他类,其他库,第三方
  • 我需要什么层次的抽象来提供? 难道消费者了解位操作。
  • 请问我已经有来自VB / C#等的接口?

有一个伟大的书“的大型C ++软件设计”,这促进基本类型外,如果你能避免另一头文件/接口扶养你应该尝试。

如果您使用的是Qt,你应该去看看的QFlags。 该QFlags类提供储存或组合枚举值的类型安全的方法。

我宁愿去

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

很简单,因为:

  1. 这是更清洁,它使代码可读性和可维护性。
  2. 它在逻辑组的常量。
  3. 程序员的时间更重要的是,除非的任务是拯救那些3个字节。

这并不是说我喜欢过度设计的一切,但有时在这些情况下,它可能是值得创建(小)类来封装这些信息。 如果你创建一个类记录类型,那么它可能有这样的功能:

无效setDeleted();

无效clearDeleted();

请将isDeleted布尔();

等等...(或任何约定的西装)

它可以验证组合(因为不是所有的组合都是合法的,例如,如果'新'和'删除'不能同时设置在同一时间的情况下)。 如果你只是使用等那么设置状态代码需要验证位掩码,一类可以封装的逻辑了。

该类还可以给你附加有意义的日志信息,以每个国家的能力,你可以添加一个函数返回当前状态等的字符串表示形式(或使用流媒体运营商“<<”)。

对于这一切,如果你担心存储你仍然可以有类只有一个“字符”数据成员,所以只需要少量的存储空间(假设它是非虚拟的)。 当然,这取决于硬件等你可能有对齐问题。

你可以有实际的位值不可见的“世界”的其余部分,如果他们在里面cpp文件,而不是在头文件在一个匿名的命名空间。

如果您发现使用枚举/#代码定义/掩码等有很多的“支持”代码来处理无效组合,测井等再封装在一个类中可能是值得考虑的。 当然,最次的简单问题最好还是用简单的解决方案...

分类:C# 时间:2015-03-14 人气:0
分享到:

相关文章

Copyright (C) 55228885.com, All Rights Reserved.

55228885 版权所有 京ICP备15002868号

processed in 0.511 (s). 10 q(s)