C++ 类型安全枚举位标志

C++ 类型安全枚举位标志,c++,enums,bitwise-operators,c++03,C++,Enums,Bitwise Operators,C++03,我希望为我的当前问题使用一组位标志。这些标志(很好地)定义为枚举的一部分,但是我知道当您从一个枚举中选择或两个值时,或操作的返回类型为typeint 我目前正在寻找一种解决方案,它将允许位掩码的用户保持类型安全,因此我为operator| enum ENUM { ONE = 0x01, TWO = 0x02, THREE = 0x04, FOUR = 0x08, FIVE = 0x10, SIX = 0x20

我希望为我的当前问题使用一组位标志。这些标志(很好地)定义为
枚举
的一部分,但是我知道当您从一个枚举中选择
两个值时,
操作的返回类型为type
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;
}
enum枚举
{
一=0x01,
二=0x02,
三=0x04,
四=0x08,
5=0x10,
六=0x20
};
枚举运算符|(枚举lhs,枚举rhs)
{
//首先强制转换为int,否则我们只会递归
返回static_cast(static_cast(lhs)| static_cast(rhs));
}
无效枚举测试(枚举v)
{
}
int main(int argc,字符**argv)
{
//对enumTest的有效调用
抽样测试(一|二|五);
抽样测试(二|三|四|五);
枚举测试(一|二|三|四|五|六);
返回0;
}

这种过载真的提供了类型安全性吗?强制转换包含枚举中未定义的值的
int
是否会导致未定义的行为?有什么需要注意的吗?

常量的值在或下未关闭。换句话说,一个或两个枚举常量的结果可能会产生一个非枚举常量的值:

0x30 == FIVE | SIX;
标准说这没问题,一个枚举可以有一个不等于它的任何枚举数(常量)的值。大概是为了允许这种类型的使用

在我看来,这不是类型安全的,因为如果要查看
enumTest
的实现,您必须知道参数类型是
ENUM
,但它的值可能不是
ENUM
枚举数


我认为,如果这些只是位标志,那么就按照编译器的要求执行:使用
int
组合标志。

和简单的
enum
,例如您的:

enum ENUM
{
    ONE     = 0x01,
    TWO     = 0x02,
    ...
};
底层类型(很可能是
int
)1是由实现定义的,但只要您要使用
|
(按位或)创建掩码,结果将永远不需要比此枚举中最大值更宽的类型



[1]“枚举的基础类型是整数类型,可以表示枚举中定义的所有枚举数值。实现定义了使用哪个整数类型作为枚举的基础类型,但基础类型不得大于
int
,除非枚举数的值不能放入
int
无符号int
。“

如果您考虑类型安全,最好使用

enum位{A,B,C,D};
std::位集bset,bset1;
b组(A);b组(C);
bset1[B]=1;
断言(bset[A]==bset[C]);
断言(B集[A]!=B集[B]);
断言(bset1!=bset);
这种过载真的提供了类型安全性吗

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

强制转换包含枚举中未定义的值的int是否会导致未定义的行为

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

有什么需要注意的事项吗


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

这是我处理位标志的方法:

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)
然后,
ONE+TWO
仍然是类型
ENUM
。您可以重用该类来定义多个不同、不兼容类型的位标志集

我个人不喜欢使用
&
来设置和测试位。设置和测试需要进行逻辑运算,但除非您考虑按位运算,否则它们不会表达运算的含义。如果你读出一、两个,你可能会认为你想要一个或两个,不一定两者都要。这就是为什么我更喜欢使用
+
将标志添加到一起,并使用
=
测试是否设置了标志


有关我建议的实现的更多详细信息,请参阅。

运算符|(五,六)==0x30
。什么枚举常量的值为0x30?我不会对结果值进行任何形式的比较,我只是检查标志。那么OR的结果应该保持为int。@Adam:为什么?OR的结果是此enumeration.BTW的有效值。Qt做了OP用模板尝试的事情QFlags你可能想看看。只要结果是一个有效的枚举值,强制转换就可以很好地定义,就像这里一样。有效值不仅是命名的枚举数,而且(至少)是二次幂的下一个最大值-它是这样指定的,以允许这种用法。@MikeSeymour您对此有参考吗?我发现的所有东西都说这是非法的。第7段是最相关的,因为它定义了值的范围。@MikeSeymour OP专门询问C++03。@MikeSeymour我想你指的是这句话:“可以定义一个枚举,它的值不是由任何枚举器定义的。”by“只要值可以由枚举表示”,您的意思是这些值必须由枚举的基础类型表示吗?
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);