在我正在研究的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++,没有#define
s,我在很少的空间中使用了名称空间和模板,所以这些也不是问题.
结合策略以减少单一方法的缺点.我在嵌入式系统中工作,因此以下解决方案基于整数和按位运算符快速,低内存和低闪存使用率这一事实.
将枚举放在命名空间中以防止常量污染全局命名空间.
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; }
确保正确价值安全的唯一方法是使用具有操作员重载的专用类,并将其作为另一个读者的练习.
他们会污染你的代码.
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
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. } }
如果你想减少耦合(即能够隐藏常量的值,等等,根据需要修改它们而不需要完全重新编译),你可以在标题中将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
你排除了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个定义应该看起来相同.
这里有几篇关于const与宏与枚举的文章:
符号常量
枚举常量与常量对象
我认为你应该避免宏,特别是因为你写了大部分新代码是在现代C++中.
如果可能,请勿使用宏.在现代C++方面,它们并不太受人尊敬.