在c#中,通过[flags]属性将枚举视为标志,但在c++中实现这一点的最佳方法是什么?

例如,我想写:

enum AnimalFlags
{
    HasClaws = 1,
    CanFly =2,
    EatsFish = 4,
    Endangered = 8
};

seahawk.flags = CanFly | EatsFish | Endangered;

然而,我得到编译器错误关于int/enum转换。除了生硬的角色转换,还有更好的表达方式吗?最好,我不想依赖第三方库(如boost或Qt)的构造。

编辑:如答案中所示,我可以通过声明seahawk来避免编译器错误。标记为int。但是,我希望有某种机制来执行类型安全,这样就不能编写seahawk了。flags = HasMaximizeButton。


当前回答

另一个宏解决方案,但与现有的答案不同,它没有使用reinterpret_cast(或C-cast)在enum&t和Int&之间进行强制转换,这在标准c++中是禁止的(参见本文)。

#define MAKE_FLAGS_ENUM(TEnum, TUnder)                                                                                             \
TEnum  operator~  ( TEnum  a          ) { return static_cast<TEnum> (~static_cast<TUnder> (a)                           ); }  \
TEnum  operator|  ( TEnum  a, TEnum b ) { return static_cast<TEnum> ( static_cast<TUnder> (a) |  static_cast<TUnder>(b) ); }  \
TEnum  operator&  ( TEnum  a, TEnum b ) { return static_cast<TEnum> ( static_cast<TUnder> (a) &  static_cast<TUnder>(b) ); }  \
TEnum  operator^  ( TEnum  a, TEnum b ) { return static_cast<TEnum> ( static_cast<TUnder> (a) ^  static_cast<TUnder>(b) ); }  \
TEnum& operator|= ( TEnum& a, TEnum b ) { a = static_cast<TEnum>(static_cast<TUnder>(a) | static_cast<TUnder>(b) ); return a; }  \
TEnum& operator&= ( TEnum& a, TEnum b ) { a = static_cast<TEnum>(static_cast<TUnder>(a) & static_cast<TUnder>(b) ); return a; }  \
TEnum& operator^= ( TEnum& a, TEnum b ) { a = static_cast<TEnum>(static_cast<TUnder>(a) ^ static_cast<TUnder>(b) ); return a; }

失去reinterpret_cast意味着我们不能再依赖x |= y语法,但是通过将这些扩展为x = x | y形式,我们就不再需要它了。

注意:你可以使用std::underlying_type来获取TUnder,为了简洁,我没有包括它。

其他回答

下面是我的解决方案,不需要任何一堆重载或强制转换:

namespace EFoobar
{
    enum
    {
        FB_A    = 0x1,
        FB_B    = 0x2,
        FB_C    = 0x4,
    };
    typedef long Flags;
}

void Foobar(EFoobar::Flags flags)
{
    if (flags & EFoobar::FB_A)
        // do sth
        ;
    if (flags & EFoobar::FB_B)
        // do sth
        ;
}

void ExampleUsage()
{
    Foobar(EFoobar::FB_A | EFoobar::FB_B);
    EFoobar::Flags otherflags = 0;
    otherflags|= EFoobar::FB_B;
    otherflags&= ~EFoobar::FB_B;
    Foobar(otherflags);
}

我认为这是可以的,因为我们无论如何都会识别(非强类型)枚举和整数。

只是作为一个(较长的)边注,如果你

要使用强类型枚举和 不需要重一点摆弄你的旗帜 性能不是问题

我会想到这个:

#include <set>

enum class EFoobarFlags
{
    FB_A = 1,
    FB_B,
    FB_C,
};

void Foobar(const std::set<EFoobarFlags>& flags)
{
    if (flags.find(EFoobarFlags::FB_A) != flags.end())
        // do sth
        ;
    if (flags.find(EFoobarFlags::FB_B) != flags.end())
        // do sth
        ;
}

void ExampleUsage()
{
    Foobar({EFoobarFlags::FB_A, EFoobarFlags::FB_B});
    std::set<EFoobarFlags> otherflags{};
    otherflags.insert(EFoobarFlags::FB_B);
    otherflags.erase(EFoobarFlags::FB_B);
    Foobar(otherflags);
}

使用c++ 11初始化列表和枚举类。

我发现自己也在问同样的问题,并提出了一个基于c++ 11的通用解决方案,类似于soru的方案:

template <typename TENUM>
class FlagSet {

private:
    using TUNDER = typename std::underlying_type<TENUM>::type;
    std::bitset<std::numeric_limits<TUNDER>::max()> m_flags;

public:
    FlagSet() = default;

    template <typename... ARGS>
    FlagSet(TENUM f, ARGS... args) : FlagSet(args...)
    {   
        set(f);
    }   
    FlagSet& set(TENUM f)
    {   
        m_flags.set(static_cast<TUNDER>(f));
        return *this;
    }   
    bool test(TENUM f)
    {   
        return m_flags.test(static_cast<TUNDER>(f));
    }   
    FlagSet& operator|=(TENUM f)
    {   
        return set(f);
    }   
};

界面可以根据口味进行改进。那么它可以这样使用:

FlagSet<Flags> flags{Flags::FLAG_A, Flags::FLAG_C};
flags |= Flags::FLAG_D;

目前还没有语言支持枚举标志,如果它将成为c++标准的一部分,元类可能会固有地添加这个特性。

我的解决方案是创建仅枚举实例化的模板函数,为枚举类使用其底层类型添加类型安全的按位操作支持:

文件:EnumClassBitwise.h

#pragma once
#ifndef _ENUM_CLASS_BITWISE_H_
#define _ENUM_CLASS_BITWISE_H_

#include <type_traits>

//unary ~operator    
template <typename Enum, typename std::enable_if_t<std::is_enum<Enum>::value, int> = 0>
constexpr inline Enum& operator~ (Enum& val)
{
    val = static_cast<Enum>(~static_cast<std::underlying_type_t<Enum>>(val));
    return val;
}

// & operator
template <typename Enum, typename std::enable_if_t<std::is_enum<Enum>::value, int> = 0>
constexpr inline Enum operator& (Enum lhs, Enum rhs)
{
    return static_cast<Enum>(static_cast<std::underlying_type_t<Enum>>(lhs) & static_cast<std::underlying_type_t<Enum>>(rhs));
}

// &= operator
template <typename Enum, typename std::enable_if_t<std::is_enum<Enum>::value, int> = 0>
constexpr inline Enum operator&= (Enum& lhs, Enum rhs)
{
    lhs = static_cast<Enum>(static_cast<std::underlying_type_t<Enum>>(lhs) & static_cast<std::underlying_type_t<Enum>>(rhs));
    return lhs;
}

//| operator

template <typename Enum, typename std::enable_if_t<std::is_enum<Enum>::value, int> = 0>
constexpr inline Enum operator| (Enum lhs, Enum rhs)
{
    return static_cast<Enum>(static_cast<std::underlying_type_t<Enum>>(lhs) | static_cast<std::underlying_type_t<Enum>>(rhs));
}
//|= operator

template <typename Enum, typename std::enable_if_t<std::is_enum<Enum>::value, int> = 0>
constexpr inline Enum& operator|= (Enum& lhs, Enum rhs)
{
    lhs = static_cast<Enum>(static_cast<std::underlying_type_t<Enum>>(lhs) | static_cast<std::underlying_type_t<Enum>>(rhs));
    return lhs;
}

#endif // _ENUM_CLASS_BITWISE_H_

为了方便和减少错误,你可能想要包装你的枚举和整数的位标志操作:

文件:BitFlags.h

#pragma once
#ifndef _BIT_FLAGS_H_
#define _BIT_FLAGS_H_

#include "EnumClassBitwise.h"

 template<typename T>
 class BitFlags
 {
 public:

     constexpr inline BitFlags() = default;
     constexpr inline BitFlags(T value) { mValue = value; }
     constexpr inline BitFlags operator| (T rhs) const { return mValue | rhs; }
     constexpr inline BitFlags operator& (T rhs) const { return mValue & rhs; }
     constexpr inline BitFlags operator~ () const { return ~mValue; }
     constexpr inline operator T() const { return mValue; }
     constexpr inline BitFlags& operator|=(T rhs) { mValue |= rhs; return *this; }
     constexpr inline BitFlags& operator&=(T rhs) { mValue &= rhs; return *this; }
     constexpr inline bool test(T rhs) const { return (mValue & rhs) == rhs; }
     constexpr inline void set(T rhs) { mValue |= rhs; }
     constexpr inline void clear(T rhs) { mValue &= ~rhs; }

 private:
     T mValue;
 };
#endif //#define _BIT_FLAGS_H_

可能的用法:

#include <cstdint>
#include <BitFlags.h>
void main()
{
    enum class Options : uint32_t
    { 
          NoOption = 0 << 0
        , Option1  = 1 << 0
        , Option2  = 1 << 1
        , Option3  = 1 << 2
        , Option4  = 1 << 3
    };

    const uint32_t Option1 = 1 << 0;
    const uint32_t Option2 = 1 << 1;
    const uint32_t Option3 = 1 << 2;
    const uint32_t Option4 = 1 << 3;

   //Enum BitFlags
    BitFlags<Options> optionsEnum(Options::NoOption);
    optionsEnum.set(Options::Option1 | Options::Option3);

   //Standard integer BitFlags
    BitFlags<uint32_t> optionsUint32(0);
    optionsUint32.set(Option1 | Option3); 

    return 0;
}

c++标准明确讨论了这一点,请参见“17.5.2.1.3位掩码类型”部分:

http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2012/n3485.pdf

给定这个“模板”,你会得到:

enum AnimalFlags : unsigned int
{
    HasClaws = 1,
    CanFly = 2,
    EatsFish = 4,
    Endangered = 8
};

constexpr AnimalFlags operator|(AnimalFlags X, AnimalFlags Y) {
    return static_cast<AnimalFlags>(
        static_cast<unsigned int>(X) | static_cast<unsigned int>(Y));
}

AnimalFlags& operator|=(AnimalFlags& X, AnimalFlags Y) {
    X = X | Y; return X;
}

其他的运算符也一样。 还要注意“constexpr”,如果您希望编译器能够在编译时执行操作符,则需要使用它。

如果你正在使用c++ /CLI并且希望能够分配给ref类的enum成员,你需要使用跟踪引用:

AnimalFlags% operator|=(AnimalFlags% X, AnimalFlags Y) {
    X = X | Y; return X;
}

注意:此示例不完整,请参见“17.5.2.1.3位掩码类型”小节获得完整的操作符集。

如上(启)或做以下。实际上枚举是“枚举”,你想做的是有一个集合,因此你应该使用stl::set

enum AnimalFlags
{
    HasClaws = 1,
    CanFly =2,
    EatsFish = 4,
    Endangered = 8
};

int main(void)
{
    AnimalFlags seahawk;
    //seahawk= CanFly | EatsFish | Endangered;
    seahawk= static_cast<AnimalFlags>(CanFly | EatsFish | Endangered);
}