追求更好的bitflag枚举

时间:2018-04-04 14:39:16

标签: c++ enums bit-manipulation enum-flags

好的,我们已经在C ++ 17了,对C ++中一个非常好的bitflags接口仍然没有一个令人满意的答案。

我们有enum将其成员值放入封闭范围,但是隐式转换为它们的基础类型,因此可以用作 - 如果它们是位标志但是拒绝重新分配回枚举而不进行转换

我们enum class解决了名称范围问题,因此它们的值必须明确命名为MyEnum::MyFlag甚至MyClass::MyEnum::MyFlag,但它们不会隐式转换为其基础类型,因此不能被用作比特标志而不会无休止地来回传播。

最后,我们有来自C的旧位字段,例如:

struct FileFlags {
   unsigned ReadOnly : 1;
   unsigned Hidden : 1;
   ...
};

其缺点是没有好的方法将整个自身初始化 - 人们不得不求助于使用memset或者转换地址或类似地来覆盖整个值或者一次初始化它或者一次操作多个位。它也无法命名给定标志的值,而不是它的地址 - 因此没有名称代表0x02,而使用枚举时有这样的名称,因此枚举很容易命名标记的组合,例如FileFlags::ReadOnly | FileFlags::Hidden - 对于位字段来说,这并不是一个很好的方式。

此外,我们仍然使用简单的constexpr#define来命名位值,然后根本不使用枚举。这有效,但完全将位值与基础位标志类型分离。也许这最终不是最糟糕的方法,特别是如果一个结构中的位标志值为constexpr以给它们自己的名称范围?

struct FileFlags {
    constexpr static uint16_t ReadOnly = 0x01u;
    constexpr static uint16_t Hidden = 0x02u;
    ...
}

所以,就目前情况而言,我们有很多技巧,但没有一种方法可以说明一个非常可靠的方法

  

这是一个具有以下有效位标志的类型,它有自己的名称范围,这些位和类型应该可以与标准的按位运算符一起使用,例如| &安培; ^〜,它们应该与0之类的整数值相当,任何按位运算符的结果都应该保留为命名类型,而不是转换为整数

所有这些都说,有很多尝试在C ++中尝试生成上述实体 -

  1. Windows操作系统团队开发了一个简单的宏,它生成C ++代码,以定义给定枚举类型DEFINE_ENUM_FLAG_OPERATORS(EnumType)上必要的缺失运算符,然后定义运算符。 &安培; ^〜和相关的赋值操作,如| =等等。
  2. ' grisumbras'有一个公共GIT项目,用于启用带有范围的枚举here的bitflag语义,它使用enable_if元编程允许给定的枚举转换为支持缺失运算符的位标志类型,然后再静默返回。
  3. 在不知情的情况下,我编写了一个相对简单的bit_flags包装器,它定义了自身的所有按位运算符,因此可以使用bit_flags<EnumType> flags,然后flags具有按位语义。这不能做的是允许枚举的基本实际上直接正确处理按位运算符,所以即使使用EnumType::ReadOnly | EnumType::Hidden也不能说bit_flags<EnumType>因为底层枚举本身仍然不支持必要运营商。我不得不最终做同样的事情,基本上是上面的#1和#2,并通过要求用户为其枚举的元类型声明特殊化来为各种按位运算符启用operator | (EnumType, EnumType),例如{{1} }
  4. 最终,#1,#2和#3的问题在于(就我所知)在枚举本身(如#1中)定义缺少的运算符或定义必要的不可能(据我所知)启用程序类型(例如,template <> struct is_bitflag_enum<EnumType> : std::true_type {};,如#2,部分#3),类范围。这些必须发生在类或结构之外,因为C ++根本没有一个我知道的机制,它允许我在一个类中进行这样的声明。

    所以现在,我希望有一组标志应该作为给定类的范围,但我不能在类标题中使用这些标志(例如默认初始化,内联函数等),因为我无法启用任何允许枚举被视为bitflags的机制,直到类定义的右括号之后。或者,我可以在它们所属的类之外定义所有这些标志枚举,以便我可以调用&#34;将此枚举变为按位类型&#34;在用户类定义之前,要在客户端类中充分利用该功能 - 但现在位标志位于外部范围内,而不是与类本身相关联。

    这不是世界末日 - 以上都不是。但是所有这些都会在编写我的代码时引起无穷无尽的麻烦 - 并阻止我以最自然的方式编写代码 - 即使用给定的flag-enum属于特定类(在作用域内)的客户端类,但具有按位标志-semantics(我的方法#3几乎允许这个 - 只要一切都被bit_flags包装 - 显式启用所需的按位兼容性。)

    所有这些仍然让我感到烦人的感觉,这可能比它更好!

    肯定应该 - 也许是但我还没想到它 - 接近枚举以启用它们的按位运算符,同时允许它们在封闭的类范围内声明和使用。

    有没有人有一个我上面没有考虑过的wip或方法,这将使我成为所有可能世界中最好的一个&#34;在这?

5 个答案:

答案 0 :(得分:1)

例如

// union only for convenient bit access. 
typedef union a
{ // it has its own name-scope
    struct b
     {
         unsigned b0 : 1;
         unsigned b2 : 1;
         unsigned b3 : 1;
         unsigned b4 : 1;
         unsigned b5 : 1;
         unsigned b6 : 1;
         unsigned b7 : 1;
         unsigned b8 : 1;
         //...
     } bits;
    unsigned u_bits;
    // has the following valid bit-flags in it
    typedef enum {
        Empty = 0u,
        ReadOnly = 0x01u,
        Hidden  = 0x02u
    } Values;
    Values operator =(Values _v) { u_bits = _v; return _v; }
     // should be freely usable with standard bitwise operators such as | & ^ ~   
    union a& operator |( Values _v) { u_bits |= _v; return *this; }
    union a& operator &( Values _v) { u_bits &= _v; return *this; }
    union a& operator |=( Values _v) { u_bits |= _v; return *this; }
    union a& operator &=( Values _v) { u_bits &= _v; return *this; }
     // ....
    // they should be comparable to integral values such as 0
    bool operator <( unsigned _v) { return u_bits < _v; }
    bool operator >( unsigned _v) { return u_bits > _v; }
    bool operator ==( unsigned _v) { return u_bits == _v; }
    bool operator !=( unsigned _v) { return u_bits != _v; }
} BITS;


int main()
 {
     BITS bits;
     int integral = 0;

     bits = bits.Empty;

     // they should be comparable to integral values such as 0
     if ( bits == 0)
     {
         bits = bits.Hidden;
         // should be freely usable with standard bitwise operators such as | & ^ ~
         bits = bits | bits.ReadOnly;
         bits |= bits.Hidden;
         // the result of any bitwise operators should remain the named type, and not devolve into an integral
         //bits = integral & bits; // error
         //bits |= integral; // error
     }
 }

答案 1 :(得分:1)

您可以在将枚举作为值的封闭类内部使用好友函数。可以在宏中使用它来定义必要的功能,而这些功能都在类范围内。

例如,要避免is_bitflag_enum特性要专门化,请专门化一个包含枚举和运算符的结构。就像#2一样,仍然不能在课堂上完成。

#include <type_traits>

template<class Tag>
struct bitflag {
    enum class type;

#define DEFINE_BITFLAG_OPERATOR(OP) \
    friend constexpr type operator OP(type lhs, type rhs) noexcept { \
        typedef typename ::std::underlying_type<type>::type underlying; \
        return static_cast<type>(static_cast<underlying>(lhs) OP static_cast<underlying>(rhs)); \
    } \
    friend constexpr type& operator OP ## = (type& lhs, type rhs) noexcept { \
        return (lhs = lhs OP rhs); \
    }

    DEFINE_BITFLAG_OPERATOR(|)
    DEFINE_BITFLAG_OPERATOR(&)
    DEFINE_BITFLAG_OPERATOR(^)

#undef DEFINE_BITFLAG_OPERATOR

#define DEFINE_BITFLAG_OPERATOR(OP) \
    friend constexpr bool operator OP(type lhs, typename ::std::underlying_type<type>::type rhs) noexcept { \
        return static_cast<typename ::std::underlying_type<type>::type>(lhs) OP rhs; \
    } \
    friend constexpr bool operator OP(typename ::std::underlying_type<type>::type lhs, type rhs) noexcept { \
        return lhs OP static_cast<typename ::std::underlying_type<type>::type>(rhs); \
    }

    DEFINE_BITFLAG_OPERATOR(==)
    DEFINE_BITFLAG_OPERATOR(!=)
    DEFINE_BITFLAG_OPERATOR(<)
    DEFINE_BITFLAG_OPERATOR(>)
    DEFINE_BITFLAG_OPERATOR(>=)
    DEFINE_BITFLAG_OPERATOR(<=)

#undef DEFINE_BITFLAG_OPERATOR

    friend constexpr type operator~(type e) noexcept {
        return static_cast<type>(~static_cast<typename ::std::underlying_type<type>::type>(e));
    }

    friend constexpr bool operator!(type e) noexcept {
        return static_cast<bool>(static_cast<typename ::std::underlying_type<type>::type>(e));
    }
};

// The `struct file_flags_tag` (Which declares a new type) differentiates between different
// enum classes declared
template<> enum class bitflag<struct file_flags_tag>::type {
    none = 0,
    readable = 1 << 0,
    writable = 1 << 1,
    executable = 1 << 2,
    hidden = 1 << 3
};

using file_flags = bitflag<file_flags_tag>::type;

bool is_executable(file_flags f) {
    return (f & file_flags::executable) == 0;
}

您还可以制作一个宏来定义每个朋友功能。就像#1,但都在类范围内。

#include <type_traits>

#define MAKE_BITFLAG_FRIEND_OPERATORS_BITWISE(OP, ENUM_TYPE) \
    friend constexpr ENUM_TYPE operator OP(ENUM_TYPE lhs, ENUM_TYPE rhs) noexcept { \
        typedef typename ::std::underlying_type<ENUM_TYPE>::type underlying; \
        return static_cast<ENUM_TYPE>(static_cast<underlying>(lhs) OP static_cast<underlying>(rhs)); \
    } \
    friend constexpr ENUM_TYPE& operator OP ## = (ENUM_TYPE& lhs, ENUM_TYPE rhs) noexcept { \
        return (lhs = lhs OP rhs); \
    }

#define MAKE_BITFLAG_FRIEND_OPERATORS_BOOLEAN(OP, ENUM_TYPE) \
    friend constexpr bool operator OP(ENUM_TYPE lhs, typename ::std::underlying_type<ENUM_TYPE>::type rhs) noexcept { \
        return static_cast<typename ::std::underlying_type<ENUM_TYPE>::type>(lhs) OP rhs; \
    } \
    friend constexpr bool operator OP(typename ::std::underlying_type<ENUM_TYPE>::type lhs, ENUM_TYPE rhs) noexcept { \
        return lhs OP static_cast<typename ::std::underlying_type<ENUM_TYPE>::type>(rhs); \
    }


#define MAKE_BITFLAG_FRIEND_OPERATORS(ENUM_TYPE) \
    public: \
    MAKE_BITFLAG_FRIEND_OPERATORS_BITWISE(|, ENUM_TYPE) \
    MAKE_BITFLAG_FRIEND_OPERATORS_BITWISE(&, ENUM_TYPE) \
    MAKE_BITFLAG_FRIEND_OPERATORS_BITWISE(^, ENUM_TYPE) \
    MAKE_BITFLAG_FRIEND_OPERATORS_BOOLEAN(==, ENUM_TYPE) \
    MAKE_BITFLAG_FRIEND_OPERATORS_BOOLEAN(!=, ENUM_TYPE) \
    MAKE_BITFLAG_FRIEND_OPERATORS_BOOLEAN(<, ENUM_TYPE) \
    MAKE_BITFLAG_FRIEND_OPERATORS_BOOLEAN(>, ENUM_TYPE) \
    MAKE_BITFLAG_FRIEND_OPERATORS_BOOLEAN(>=, ENUM_TYPE) \
    MAKE_BITFLAG_FRIEND_OPERATORS_BOOLEAN(<=, ENUM_TYPE) \
    friend constexpr ENUM_TYPE operator~(ENUM_TYPE e) noexcept { \
        return static_cast<ENUM_TYPE>(~static_cast<typename ::std::underlying_type<ENUM_TYPE>::type>(e)); \
    } \
    friend constexpr bool operator!(ENUM_TYPE e) noexcept { \
        return static_cast<bool>(static_cast<typename ::std::underlying_type<ENUM_TYPE>::type>(e)); \
    }

// ^ The above in a header somewhere

class my_class {
public:
    enum class my_flags {
        none = 0, flag_a = 1 << 0, flag_b = 1 << 2
    };

    MAKE_BITFLAG_FRIEND_OPERATORS(my_flags)

    bool has_flag_a(my_flags f) {
        return (f & my_flags::flag_a) == 0;
    }
};

答案 2 :(得分:0)

我将enum class与以下模板运算符一起使用:

template< typename ENUM, typename std::enable_if< std::is_enum< ENUM >::value, int >::type* = nullptr >
inline ENUM operator |( ENUM lhs, ENUM rhs )
{
    return static_cast< ENUM >( static_cast< UInt32 >( lhs ) | static_cast< UInt32 >( rhs ));
}

template< typename ENUM, typename std::enable_if< std::is_enum< ENUM >::value, int >::type* = nullptr >
inline ENUM& operator |=( ENUM& lhs, ENUM rhs )
{
    lhs = lhs | rhs;
    return lhs;
}

template< typename ENUM, typename std::enable_if< std::is_enum< ENUM >::value, int >::type* = nullptr >
inline UInt32 operator &( ENUM lhs, ENUM rhs )
{
    return static_cast< UInt32 >( lhs ) & static_cast< UInt32 >( rhs );
}

template< typename ENUM, typename std::enable_if< std::is_enum< ENUM >::value, int >::type* = nullptr >
inline ENUM& operator &=( ENUM& lhs, ENUM rhs )
{
    lhs = lhs & rhs;
    return lhs;
}

template< typename ENUM, typename std::enable_if< std::is_enum< ENUM >::value, int >::type* = nullptr >
inline ENUM& operator &=( ENUM& lhs, int rhs )
{
    lhs = static_cast< ENUM >( static_cast< int >( lhs ) & rhs );
    return lhs;
}

如果您担心上述运算符泄漏到其他枚举中,我想您可以将它们封装在声明该枚举的同一个命名空间中,甚至可以逐个枚举地实现它们(我以前使用的宏)。总体而言,我考虑过大了,现在让它们在我的顶级名称空间中声明以供任何代码使用。

答案 3 :(得分:0)

我采用Xaqq's FlagSet on Code Review SE的方法。

关键是要引入一种新类型,以用作固定选项列表中一个或多个接通值的“容器”。所述容器是bitset周围的包装器,它以范围枚举的实例作为输入。

归因于范围限定的枚举,它是类型安全的,并且可以通过委派给位集操作的操作符重载来执行按位操作。而且,如果您愿意并且不需要按位运算或存储多个标志,则仍可以直接使用作用域枚举。

在生产中,我确实对链接的代码进行了一些更改;其中一些将在“代码审查”页面的注释中进行讨论。

答案 4 :(得分:-1)

使用基础整数类型选择实现自己的bitset并不困难。枚举的问题在于缺少适应bitset的必要元信息。但是仍然使用适当的元编程和标志启用特征,可以使用这样的语法:

flagset<file_access_enum>   rw = bit(read_access_flag)|bit(write_access_flag);