键入安全枚举位标志

时间:2013-10-09 09:20:14

标签: c++ enums bitwise-operators c++03

我希望为我当前的问题使用一组位标志。这些标志(很好地)被定义为enum的一部分,但是我理解当你OR来自枚举的两个值时,OR操作的返回类型具有类型{{1} }。

我目前正在寻找的是一种解决方案,它允许位掩码的用户保持类型安全,因此我为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; } 值会导致未定义的行为吗?有什么需要注意的注意事项吗?

5 个答案:

答案 0 :(得分:6)

  

这种过载真的能提供类型安全吗?

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

  

转换包含未在枚举中定义的值的int会导致未定义的行为吗?

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

  

有什么需要注意的注意事项吗?

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

答案 1 :(得分: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);

答案 2 :(得分:3)

在OR下不会关闭常量的值。换句话说,两个ENUM常量的OR结果可能会产生一个不是ENUM常量的值:

0x30 == FIVE | SIX;

标准说这没关系,一个咒语可以有一个不等于任何一个煽动者(常数)的值。大概是允许这种用法。

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

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

答案 3 :(得分:2)

使用简单的enum,例如你的:

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

它是实现定义的底层类型(最有可能是int 1 ,但只要您打算使用|(按位或)来创建掩码,结果永远不会需要比此枚举中最大值更宽的类型。


[1] “枚举的基础类型是一个整数类型,可以表示枚举中定义的所有枚举器值。它是实现定义的哪个整数类型使用作为枚举的基础类型,但基础类型不得大于int,除非枚举值不适合int或{{1 }} “。

答案 4 :(得分: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,你可能认为你想要一个或两个,不一定两个。这就是为什么我更喜欢使用+一起添加标记和==来测试是否设置了标记。

有关我建议的实施的详细信息,请参阅this blog post