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

空间要求是很重要的(byte vs int),但不是至关重要的。与定义了我失去一类型的安全,并与 enum 我失去了一些空间(整数)和可能需要投的时候我想做一个按位的操作。与 const 我想我也输类型的安全,因为随机的 uint8 可以在获得通过的错误。

是否有其他一些更清洁的方式?

如果不是,你会怎么使用和为什么?

P.S.其余的代码是相当清洁的现代C++没有 #defines,和我已经使用的名称空间和模板中的几个空间,使那些不是出的问题。

有帮助吗?

解决方案

结合的战略,以减少缺点的一个单一的方法。我的工作中嵌入式系统,从而下的解决方案是基于这样的事实,整数和位运营商正在快速、低储低在闪光使用情况。

地方枚举,在一个名字空间,以防止常量从污染全球命名空间。

namespace RecordType {

一枚举的宣布和定义汇编时间的检查类型。总是使用编制的时间类型检查,以确保参数和变量给出正确的类型。没有必要为typedef在C++。

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

只有这样,才能确保正确价值的安全是使用一个专门的类操作员和重载是作为一个运动的另一个读者。

其他提示

忘记的定义

他们会污染你的代码。

bitfields?

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

永远不要用那个.你更关心的速度比以节约4int.使用点领域实际上是慢于访问的其他任何类型。

但是,位成员在结构具有实际的缺点。第一,排位在存储器化,从编译器,来编译器。此外, 许多受欢迎的编译器产生效率低下的代码阅读和书写位成员, ,和有潜在严重 线的安全问题 有关位域(尤其是在多处理系统),由于事实上,大多数机器就无法操纵的任意设置的位在存储器,但是必须,而不是载和存储个词。e.g以下不可线的安全,尽管在使用互斥的

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

如果你需要更多的理由来 使用bitfields,也许 雷蒙德*陈 会让你在他的 老新的东西 员额: 成本-效益分析的bitfields收集的布尔http://blogs.msdn.com/oldnewthing/archive/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;
}

把它们放在一个名称空间是凉爽的。如果他们宣布在加拿大养恤金计划或头文件,其价值将内联。你就可以使用的开关在这些价值,但它将稍微增加联接。

啊,是的: 去除静态的关键字.静态使用C++使用时作为你做的,如果uint8是一个夜总会的类型,你不需要这个声明这一标题包含由多个来源的相同的模块。最后,该代码应该是:

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

问题的这种方法是,你的代码知道的常数,其中略微增加的联接。

enum

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

外部const int?

如果你想减少联接(即能够隐藏价值的常数,因此,修改他们所希望的,而不需要完全重新编译),可以宣告整数作为外部的头,并且作为不断在加拿大养恤金计划的文件,如以下例子:

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

你有没有排除了性病::特集?套旗帜是什么。做

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

或者一些非常类似的-我理解的任何更正,因为我还没有测试这个。你也可以指比特的索引,但是它通常最好的定义只有一个设定的常量和类型的常量很可能更有用。

假设你已经排除了特集的,我投票 enum.

我不买那铸造这枚举是一个严重的缺点-好吧,因此它的位置和分配的范围值到一枚未定义的行为,因此,它是理论上的可能要搬起石头砸自己的脚上有些不同寻常C++实现。但是如果你只能做到这一点时,有必要(这是当从int枚举,请参考),这是完全正常的代码,人们已经看到过。

我怀疑任何空间成本枚举,太。uint8变量和参数可能不会使用任何小栈于整数,所以只有存在类事项。在某些情况下,包装多个字节在结构将赢得(在这种情况下你可以投枚举出uint8储存),但通常填补将杀死的好处无论如何。

所以枚举的有没有缺点相比有其他人,并作为一个优点给你一点的类型安全(你不可以分配一些随机的整数值不明确铸造)和清洁方面的参照的一切。

对于偏好我也把"=2"在枚举的方式。这是不必要的,但一"原则的少惊讶"表明,所有4个定义应该看起来是一样的。

这里有几个条款const与宏与枚举:

象征性的常量
枚举常量与恒的对象

我觉得你应该避免宏尤其是因为你写的最新代码现代C++。

如果可能不使用宏。他们不太多的钦佩,当它涉及到现代C++。

枚举,将是更为合适,因为他们提供"含义的标识"以及类型的安全。你可以清楚地告诉"xDeleted"是的"类型"和表示"类型的记录"(哇!) 甚至以后年。Consts会要求评论意见,他们也将需要上上下下在代码。

与定义了我失去一类型的安全

不一定...

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

和枚举,我失去了一些空间(integers)

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

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能得到通过的错误。

同样可能发生与任何这些机制:范围和价值的检查通常是正交的类型安全(虽然用户定义的类型--即你自己的课程-可以强制执行"不变"有关他们的数据)。与枚举,编译器的免费选择一个大型主办值,和一个未初始化,损坏或只是错过设enum变量可能最终仍然interpretting地位模式,为一些你不希望比较不平等的任何枚举的标识符,他们的任何组合,并0.

是否有其他一些更清洁的方式? 如果不是,你会怎么使用和为什么?

好,在末端的尝试和信任的C-风格按位或枚举工作得很好,一旦你有点领域而定经营的图片。你可以进一步提高你的稳健性与一些定义验证功能,并断言作为在mat_geek的答案;技术往往也同样适用于处理string,int,双值等。

你可能会说,这是"清洁工":

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

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

我无动于衷:位数据组的更严格的但是代码显着增长...取决于如何许多的对象您已经有,并lamdbas-美丽的,因为它们是,仍然是混乱和困难得到正确的比bitwise Or.

顺便说一句/-的参数有关的线安全是相当薄弱恕我直言-最好记住作为背景审议,而不是成为一个占主导地位的决策的驱动力;共享一个互斥的跨bitfields是一个更可能实践中,即使不知道自己包装的(互斥是相对庞大的数据成员-我必须非常关注有关性能要考虑的具有多重互斥在成员的一个目的,我会仔细看的足够注意到它们的位域)。任何子字尺寸类型可能有同样的问题(例如一个 uint8_t).无论如何,你可以尝试的原子比较和交换风格操作如果你绝望的更高的并发。

即使你必须使用4个字节的要储存一枚举(我不熟悉C++-我知道你可以指定基础类型在C#),它仍然是值得--使用枚举。

在今天和这个时代的服务器与GBs的存储器之类的东西字节4与1字节的存在应用程序级别一般不重要。当然,如果在特定情况下,存储器的使用是重要的(而且你不能让C++使用一个字来回enum),然后可以考虑'静const'的路线。

在一天结束的时候,你必须问问自己,它是值得维持的打击使用'静const'的3字节的存储器节省您的数据结构?

别的东西要记住--请参考,在x86、数据结构4个字节对准,所以除非你有一个数字节的宽元素的"记录"的结构,它可能不实际的问题。测试并确保它不会在你之前使一个折衷于维护针对性的空间。

如果你想要的类型的安全课程,以便利举法和位检查,考虑 保险箱的标签C++.我曾与提交人,他是相当聪明。

要注意,虽然。在结束,这包使用的模板 宏!

你实际上需要通过周围标志的价值观作为一个概念性的整体,或者是你要有大量的每旗的代码?无论哪种方式,我认为,具有这类或结构的1位bitfields实际上可能更清楚:

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

然后你的记录类可以有一个结构RecordFlag件的变量、职能可以采取的论点类型结构RecordFlag,等等。编译器应包bitfields在一起,节省了空间。

我可能不会使用一枚举,对于这种事情在的价值可合并在一起,更多的通常枚举是相互排斥的国家。

但无论使用哪种方法,使其更加清楚,这些都是值是位可以合并在一起,使用这种语法的实际价值来代替:

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

使用一个左转变有助于表明,每个值的目的是成为一个单位,这是不太可能以后有人会做错了什么样添加一个新的价值,并将其分配的东西一个值的9.

基于上 , 高凝聚力和低耦合, ,要求这些问题-

  • 谁需要知道?我的课我的图书馆、其他类,其他图书馆,第3次缔约方
  • 什么水平的抽象要我提供什么?不消费者明白位的行动。
  • 我将有必要界面从VB/C#等等?

那是一个伟大的书"大规模C++软件的设计",这促进基类型外,如果可以避免的另一标题的文件/口依赖你应该试试。

如果您使用的脱你应该来看看 QFlags.该QFlags类提供一种安全的方式存储或组合的枚举,值。

我宁愿去

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

只是因为:

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

不,我喜欢超过工程处的一切,但有时在这些情况下可能值得建立一个(小型)类封这样的信息。如果你创建的一个类的类型,那么它可能会有功能,如:

void setDeleted();

void clearDeleted();

bool在被删除();

等等...(或任何适合《公约》)

它可以验证的组合(在这种情况下,并不是所有的组合是合法的,例如,如果'新的'和'deleted'不能两者是设置在同一时间)。如果你只要用点口罩等那么代码,用于设置国家需要验证,a类可以封装的逻辑。

这类也可以得到你的能力附加有意义的记录的信息,以每一国家,可以添加一个功能返回串代表性的当前状态等(或使用流运营商'<<').

对于所有,如果你担心储存,你仍然可以有类只有一个'char'数据成员,因此只需要少量的储存(假定它是无虚拟).当然取决于硬件等你可能已经准的问题。

你有可能实际位价值不可见的其余部分的世界,如果他们是在一个匿名命名空间内的加拿大养恤金计划的文件,而不是在该标题的文件。

如果你找到代码使用enum/#define/位等有很多的"支持"代码处理无效组合、记录等等然后封装在一个类可能值得考虑的。当然大多数时候简单的问题,最好有简单的解决方案...

许可以下: CC-BY-SA归因
不隶属于 StackOverflow
scroll top