12

我正在为我当前的问题使用一组位标志。这些标志(很好地)定义为 an 的一部分enum,但是我知道当您OR从枚举中获取两个值时,OR操作的返回类型具有 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;
}

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

4

5 回答 5

6

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

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

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

不,只要这些值可以通过枚举来表示,它们就在这里。

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

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

于 2013-10-09T09:56:12.423 回答
4

If you think about type safety, it is better to use 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);
于 2013-10-09T09:34:34.800 回答
3

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

0x30 == FIVE | SIX;

标准说这没关系,枚举可以有一个不等于它的任何枚举数(常量)的值。大概是为了允许这种类型的使用。

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

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

于 2013-10-09T09:33:03.560 回答
2

enum像你这样的简单:

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

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


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

于 2013-10-09T09:33:23.223 回答
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。您可以重新使用该类来定义多个不同、不兼容类型的位标志集。

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

有关我建议的实现的更多详细信息,请参阅此博客文章。

于 2017-01-06T23:56:13.943 回答