2013-10-09 121 views
11

我正在寻找使用一组位标志为我当前的问题。这些标志(很好地)被定义为enum的一部分,但是我明白,当您从枚举中得到两个值时,OR操作的返回类型的类型为int类型安全枚举位标志

什么目前我正在寻找的是一个解决方案,这将使该位掩码的用户保持类型安全,因此我已经创造了operator |

enum ENUM 
{ 
    ONE  = 0x01, 
    TWO  = 0x02, 
    THREE = 0x04, 
    FOUR = 0x08, 
    FIVE = 0x10, 
    SIX  = 0x20 
}; 

ENUM operator | (ENUM lhs, ENUM rhs) 
{ 
    // Cast to int first otherwise we'll just end up recursing 
    return static_cast<ENUM>(static_cast<int>(lhs) | static_cast<int>(rhs)); 
} 

void enumTest(ENUM v) 
{ 
} 

int main(int argc, char **argv) 
{ 
    // Valid calls to enumTest 
    enumTest(ONE | TWO | FIVE); 
    enumTest(TWO | THREE | FOUR | FIVE); 
    enumTest(ONE | TWO | THREE | FOUR | FIVE | SIX); 

    return 0; 
} 

以下过载这是否超载真正提供类型安全?包含未在枚举中定义的值的int是否会导致未定义的行为?是否有任何警告要注意?

+0

'运营商|(五,六)== 0x30'。什么ENUM常量的值为0x30? – Adam

+0

我不会对结果值做任何形式的比较,我只是在检查标志。 – ctor

+1

然后OR的结果应该保持为int。 – Adam

回答

5

这是否超载真正提供类型安全?

在这种情况下,是的。枚举值的有效范围至少达到(但不一定包括)最大的指定枚举数之后的二的最大次幂,以便允许它用于像这样的位掩码。因此,对两个值的任何按位操作都会给出可由此类型表示的值。

是否投射包含未在enum中定义的值的int会导致未定义的行为?

不,只要这些值可由枚举表示,它们就在这里。

是否有任何需要注意的注意事项?

如果您正在进行算术等操作,可能会超出范围,那么您会得到一个实现定义的结果,但不是未定义的结果。

+0

“只要值可以通过枚举来表示”,你的意思是这些值必须可以由枚举的基础类型来表示? – Carlton

3

常量的值在OR下没有关闭。换句话说,它是可能两个ENUM常量的OR的结果将导致这不是一个枚举常量的值:

0x30 == FIVE | SIX; 

标准说,这是确定的,一个enumaration可以有一个值不等于它的任何启动器(常量)。据推测,这是允许这种类型的用法。

在我看来这不是类型安全的,因为如果你看的enumTest实施你必须意识到参数类型为ENUM,但它可能有一个值,这不是一个ENUM枚举。

我认为,如果这些只是位标志,那么做编译器希望你:使用int标志的组合。

+1

只要结果是一个有效的枚举值,演员就可以很好地定义 - 就像它在这里一样。有效值不仅仅是指定的枚举数,而是(至少)所有的一切都达到了下一个最大的两个数 - 它就是这样指定的,以便准确地使用这种用法。 –

+0

@MikeSeymour你有参考吗?我发现的一切都是非法的。 – Adam

+0

C++ 11 7.2。第7段是最相关的,因为它定义了值的范围。 –

2

用一个简单的enum如你:

enum ENUM 
{ 
    ONE  = 0x01, 
    TWO  = 0x02, 
    ... 
}; 

是实现定义什么是基本类型(很可能int),但只要你会使用|(按位或)创建掩码时,结果永远不会需要比此枚举的最大值更宽的类型。


[1]“的基本类型枚举的是,可以表示所有在枚举所定义的枚举值的整数类型,它是实现定义其积分类型用于作为除非基本类型不得大于int,除非枚举器的值不适合intunsigned int。“

4

如果你仔细想想类型安全,最好是使用std::bitset

enum BITS { A, B, C, D }; 
std::bitset<4> bset, bset1; 
bset.set(A); bset.set(C); 
bset1[B] = 1; 
assert(bset[A] == bset[C]); 
assert(bset[A] != bset[B]); 
assert(bset1 != bset); 
1

这是我的方法的位标志:

template<typename E> 
class Options { 
     unsigned long values; 
     constexpr Options(unsigned long v, int) : values{v} {} 
    public: 
     constexpr Options() : values(0) {} 
     constexpr Options(unsigned n) : values{1UL << n} {} 
     constexpr bool operator==(Options const& other) const { 
     return (values & other.values) == other.values; 
     } 
     constexpr bool operator!=(Options const& other) const { 
     return !operator==(other); 
     } 
     constexpr Options operator+(Options const& other) const { 
     return {values | other.values, 0}; 
     } 
     Options& operator+=(Options const& other) { 
     values |= other.values; 
     return *this; 
     } 
     Options& operator-=(Options const& other) { 
     values &= ~other.values; 
     return *this; 
     } 
}; 

#define DECLARE_OPTIONS(name) class name##__Tag; using name = Options 
#define DEFINE_OPTION(name, option, index) constexpr name option(index) 

您可以使用它像这样:

DECLARE_OPTIONS(ENUM); 
DEFINE_OPTIONS(ENUM, ONE, 0); 
DEFINE_OPTIONS(ENUM, TWO, 1); 
DEFINE_OPTIONS(ENUM, THREE, 2); 
DEFINE_OPTIONS(ENUM, FOUR, 3); 

然后ONE + TWO仍然ENUM类型。您可以重新使用该类来定义多个不同的不兼容类型的位标记集。

我个人不喜欢用|&来设置和测试比特。这是设置和测试需要完成的逻辑操作,但除非您考虑按位操作,否则它们不会表达操作的含义。如果你读出ONE | TWO,你可能会认为你想要一个或两个,而不一定是两个。这就是为什么我更喜欢使用+来添加标志和==来测试是否设置标志。

看到这里我的建议的执行情况的详细信息:http://www.crisluengo.net/index.php/archives/851