当前位置:  开发笔记 > 编程语言 > 正文

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

如何解决《我应该使用#define,enum还是const?》经验,为你挑选了5个好方法。

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

新纪录

删除记录

修改记录

现有记录

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

enum { xNew, xDeleted, xModified, xExisting }

但是,在代码的其他地方,我需要选择哪些记录对用户可见,所以我希望能够将其作为单个参数传递,如:

showRecords(xNew | xDeleted);

所以,似乎我有三个可能的附件:

#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++,没有#defines,我在很少的空间中使用了名称空间和模板,所以这些也不是问题.



1> mat_geek..:

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

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

namespace RecordType {

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

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

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

xInvalid = 16 };

考虑到这种类型有两个目的.跟踪记录的当前状态并创建掩码以选择某些状态的记录.创建内联函数以测试类型的值是否对您的目的有效; 作为状态标记与状态掩码.这将捕获错误,因为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 ;
}

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

确保正确价值安全的唯一方法是使用具有操作员重载的专用类,并将其作为另一个读者的练习.


@Jonathan Leffler:我认为'IsValidState'不应该这样做,'IsValidMask'是.
** - 1**运行时类型检查的想法是令人厌恶的.

2> paercebal..:

忘记定义

他们会污染你的代码.

位域?

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

不要使用它.你更关注速度而不是节约4个整体.使用位字段实际上比访问任何其他类型更慢.

然而,结构中的位成员具有实际缺点.首先,内存中位的排序因编译器而异.此外,许多流行的编译器生成用于读写位成员的低效代码,并且由于大多数机器无法操作内存中的任意位组,因此存在与位域相关的严重线程安全问题(尤其是在多处理器系统上).但必须加载并存储整个单词.例如,尽管使用了互斥锁,但以下内容并不是线程安全的

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

如果你需要更多的理由使用位域,也许Raymond Chen会在他的The Old New Thing Post中说服你:在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++中不推荐使用static,如果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?

如果你想减少耦合(即能够隐藏常量的值,等等,根据需要修改它们而不需要完全重新编译),你可以在标题中将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;
}

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


为什么你认为位域很慢?您是否实际使用它和其他方法分析代码?即使它是,清晰度可能比速度更重要,使"不要使用它"有点简化.
onebyone:第二,我在工作或家中生成的所有代码本质上都是线程安全的.它很容易做到:没有全局,没有静态,线程之间不共享,除非锁定保护.使用这个成语会破坏这个基本的线程安全性.为了什么?几个字节**也许**?... :-) ......

3> Steve Jessop..:

你排除了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);

因为bitset有一堆运算符重载,所以你现在可以做到

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

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

假设你排除了bitset,我投票给enum.

我不买那个铸件,枚举是一个严重的缺点 - 好吧所以它有点嘈杂,并且为枚举指定一个超出范围的值是未定义的行为,所以理论上可以在一些不寻常的C++上用脚射击自己实现.但是如果你只在必要时(从int到enum iirc)这样做的话,这是人们以前见过的完全正常的代码.

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

因此枚举与其他枚举相比没有任何缺点,并且作为一个优点为您提供了一些类型安全性(您无法在不显式转换的情况下分配一些随机整数值)以及干净的方式来引用所有内容.

顺便说一句,我也会在枚举中加上"= 2".这没有必要,但"最不惊讶的原则"表明所有4个定义应该看起来相同.



4> Abbas..:

这里有几篇关于const与宏与枚举的文章:

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

我认为你应该避免宏,特别是因为你写了大部分新代码是在现代C++中.



5> INS..:

如果可能,请勿使用宏.在现代C++方面,它们并不太受人尊敬.


真正.我自己讨厌宏的是,如果他们错了,你就不能介入它们.
推荐阅读
ifx0448363
这个屌丝很懒,什么也没留下!
DevBox开发工具箱 | 专业的在线开发工具网站    京公网安备 11010802040832号  |  京ICP备19059560号-6
Copyright © 1998 - 2020 DevBox.CN. All Rights Reserved devBox.cn 开发工具箱 版权所有