如何在枚举中使用枚举作为标志?
处理enum
的标志在C#中通过[Flags]
属性很好地工作,但是在C ++中这样做的最好方法是什么?
例如,我想写:
enum AnimalFlags { HasClaws = 1, CanFly =2, EatsFish = 4, Endangered = 8 }; seahawk.flags = CanFly | EatsFish | Endangered;
但是,我得到有关int
/ enum
转换的编译器错误。 有没有更好的方式来expression这一点,而不仅仅是钝器铸造? 最好,我不想依赖来自第三方库,如boost或Qt的构造。
编辑:如答案中所示,我可以通过将seahawk.flags
声明为int
来避免编译器错误。 但是,我想有一些机制来强制types安全,所以有人不能写seahawk.flags = HasMaximizeButton
。
“正确的”方法是为枚举定义位运算符,如下所示:
enum AnimalFlags { HasClaws = 1, CanFly =2, EatsFish = 4, Endangered = 8 }; inline AnimalFlags operator|(AnimalFlags a, AnimalFlags b) {return static_cast<AnimalFlags>(static_cast<int>(a) | static_cast<int>(b));}
等运营商的其余部分。 如果枚举范围超出int范围,则根据需要进行修改。
注意(也有点偏离主题):另一种做独特标志的方法可以使用位移来完成。 我,我自己,发现这更容易阅读。
enum Flags { A = 1 << 0, // binary 0001 B = 1 << 1, // binary 0010 C = 1 << 2, // binary 0100 D = 1 << 3, // binary 1000 };
它可以将值保持为一个整数,因此大多数情况下,32个标记清楚地反映在移位量中。
对于像我这样的懒惰的人,这里是复制和粘贴的模板解决scheme:
template<class T> inline T operator~ (T a) { return (T)~(int)a; } template<class T> inline T operator| (T a, T b) { return (T)((int)a | (int)b); } template<class T> inline T operator& (T a, T b) { return (T)((int)a & (int)b); } template<class T> inline T operator^ (T a, T b) { return (T)((int)a ^ (int)b); } template<class T> inline T& operator|= (T& a, T b) { return (T&)((int&)a |= (int)b); } template<class T> inline T& operator&= (T& a, T b) { return (T&)((int&)a &= (int)b); } template<class T> inline T& operator^= (T& a, T b) { return (T&)((int&)a ^= (int)b); }
seahawk.flagsvariables是什么types?
在标准C ++中,枚举不是types安全的。 他们是有效的整数。
AnimalFlags不应该是你的variables的types,你的variables应该是int,错误将消失。
像其他人所build议的hex值不需要,这没有什么区别。
枚举值的默认值为inttypes。 所以你可以肯定地按位或者合并它们并把它们放在一起并将结果存储在一个int中。
枚举types是int的受限子集,其值是枚举值之一。 因此,当您在该范围外创build一些新值时,您无法将其赋值给您的枚举types的variables。
您也可以根据需要更改枚举值types,但这个问题没有意义。
编辑:海报说,他们担心types安全,他们不希望在inttypes内不应该存在的值。
但是在AnimalFlagstypes的variables内部放置一个超出AnimalFlags范围的值将是不安全的。
有一个安全的方法来检查范围内的值虽然在inttypes…
int iFlags = HasClaws | CanFly; //InvalidAnimalFlagMaxValue-1 gives you a value of all the bits // smaller than itself set to 1 //This check makes sure that no other bits are set. assert(iFlags & ~(InvalidAnimalFlagMaxValue-1) == 0); enum AnimalFlags { HasClaws = 1, CanFly =2, EatsFish = 4, Endangered = 8, // put new enum values above here InvalidAnimalFlagMaxValue = 16 };
以上并不阻止你从一个不同的枚举值为1,2,4或8的无效标志。
如果你想绝对的types安全,那么你可以简单地创build一个std :: set,并在那里存储每个标志。 这不是节约空间,但它是types安全的,并给你像bitflag int一样的能力。
C ++ 0x注意:强types的枚举
在C ++ 0x你终于可以有types安全的枚举值….
enum class AnimalFlags { CanFly = 2, HasClaws = 4 }; if(CanFly == 2) { }//Compiling error
注意,如果你在Windows环境下工作,在WINNT.h中定义了一个DEFINE_ENUM_FLAG_OPERATORS
macros来为你完成这项工作。 所以在这种情况下,你可以这样做:
enum AnimalFlags { HasClaws = 1, CanFly =2, EatsFish = 4, Endangered = 8 }; DEFINE_ENUM_FLAG_OPERATORS(AnimalFlags) seahawk.flags = CanFly | EatsFish | Endangered;
最简单的方法是使用标准的库类位集,如图所示。
为了以types安全的方式来模拟C#特性,你必须写一个模板封装器,使用作为types参数给定的枚举来replaceint参数。 就像是:
template <class T, int N> class FlagSet { bitset<N> bits; FlagSet(T enumVal) { bits.set(enumVal); } // etc. }; enum MyFlags { FLAG_ONE, FLAG_TWO }; FlagSet<MyFlags, 2> myFlag;
我发现目前被eidolon接受的答案太危险了。 编译器的优化器可能会对枚举中的可能值作出假设,并且可能会返回无效值的垃圾回收。 通常没有人想要在标志枚举中定义所有可能的排列。
正如Brian R. Bondy在下面所述,如果你使用C ++ 11(每个人都应该这样做),那么你现在可以通过enum class
更轻松地做到这一点:
enum class ObjectType : uint32_t { ANIMAL = (1 << 0), VEGETABLE = (1 << 1), MINERAL = (1 << 2) }; constexpr enum ObjectType operator |( const enum ObjectType selfValue, const enum ObjectType inValue ) { return (enum ObjectType)(uint32_t(selfValue) | uint32_t(inValue)); } // ... add more operators here.
这通过指定枚举types来确保稳定的大小和值范围,通过使用enum class
来禁止枚举自动向下转换为整数等,并使用constexpr
来确保运算符的代码被内联,从而与常规数字一样快。
对于坚持11岁以前的C ++方言的人
如果我被一个不支持C ++ 11的编译器卡住了,我会在一个类中包装一个inttypes,然后允许只使用按位运算符和来自该枚举的types来设置它的值:
template<class ENUM,class UNDERLYING=typename std::underlying_type<ENUM>::type> class SafeEnum { public: SafeEnum() : mFlags(0) {} SafeEnum( ENUM singleFlag ) : mFlags(singleFlag) {} SafeEnum( const SafeEnum& original ) : mFlags(original.mFlags) {} SafeEnum& operator |=( ENUM addValue ) { mFlags |= addValue; return *this; } SafeEnum operator |( ENUM addValue ) { SafeEnum result(*this); result |= addValue; return result; } SafeEnum& operator &=( ENUM maskValue ) { mFlags &= maskValue; return *this; } SafeEnum operator &( ENUM maskValue ) { SafeEnum result(*this); result &= maskValue; return result; } SafeEnum operator ~() { SafeEnum result(*this); result.mFlags = ~result.mFlags; return result; } explicit operator bool() { return mFlags != 0; } protected: UNDERLYING mFlags; };
你可以像定期的枚举+ typedef那样定义它:
enum TFlags_ { EFlagsNone = 0, EFlagOne = (1 << 0), EFlagTwo = (1 << 1), EFlagThree = (1 << 2), EFlagFour = (1 << 3) }; typedef SafeEnum<enum TFlags_> TFlags;
用法也相似:
TFlags myFlags; myFlags |= EFlagTwo; myFlags |= EFlagThree; if( myFlags & EFlagTwo ) std::cout << "flag 2 is set" << std::endl; if( (myFlags & EFlagFour) == EFlagsNone ) std::cout << "flag 4 is not set" << std::endl;
你也可以使用第二个模板参数,即typedef SafeEnum<enum TFlags_,uint8_t> TFlags;
来覆盖二进制稳定枚举的基本types(如C ++ 11的enum foo : type
) 。
我使用C ++ 11的explicit
关键字标记了operator bool
覆盖,以防止导致int转换,因为这些可能会导致标记的集合在写出时折叠为0或1。 如果您不能使用C ++ 11,请将该过载保留,并将示例用法中的第一个条件重写为(myFlags & EFlagTwo) == EFlagTwo
。
在我看来,迄今没有任何答案是理想的。 为了理想,我期望解决scheme:
- 支持
==
,!=
,=
,&
,&=
,|
,常规意义上的|=
和~
操作符(即a & b
) - types安全,即不允许非枚举值(如文字或整数types)(除了枚举值的按位组合)或允许将枚举variables分配给整数types
- 允许expression式,如
if (a & b)...
- 不需要邪恶的macros,实现特定的function或其他黑客
到目前为止,大部分的解决scheme都落在了第2或第3点。WebDancer的closures在我看来是失败了,但是在第3点却没有成功,所有的枚举都需要重复。
我提出的解决scheme是一个WebDancer的广义版本,也解决了第3点:
template<typename T = typename std::enable_if<std::is_enum<T>::value, T>::type> class auto_bool { T val_; public: constexpr auto_bool(T val) : val_(val) {} constexpr operator T() const { return val_; } constexpr explicit operator bool() const { return static_cast<std::underlying_type_t<T>>(val_) != 0; } }; template <typename T = typename std::enable_if<std::is_enum<T>::value, T>::type> constexpr auto_bool<T> operator&(T lhs, T rhs) { return static_cast<T>( static_cast<typename std::underlying_type<T>::type>(lhs) & static_cast<typename std::underlying_type<T>::type>(rhs)); } template <typename T = typename std::enable_if<std::is_enum<T>::value, T>::type> constexpr T operator|(T lhs, T rhs) { return static_cast<T>( static_cast<typename std::underlying_type<T>::type>(lhs) | static_cast<typename std::underlying_type<T>::type>(rhs)); } enum class AnimalFlags : uint8_t { HasClaws = 1, CanFly = 2, EatsFish = 4, Endangered = 8 }; enum class PlantFlags : uint8_t { HasLeaves = 1, HasFlowers = 2, HasFruit = 4, HasThorns = 8 }; int main() { AnimalFlags seahawk = AnimalFlags::CanFly; // Compiles, as expected AnimalFlags lion = AnimalFlags::HasClaws; // Compiles, as expected PlantFlags rose = PlantFlags::HasFlowers; // Compiles, as expected rose = 1 // Won't compile, as expected if (seahawk != lion) {} // Compiles, as expected // if (seahawk == rose) {} // Won't compile, as expected // seahawk = PlantFlags::HasThorns; // Won't compile, as expected seahawk = seahawk | AnimalFlags::EatsFish; // Compiles, as expected lion = AnimalFlags::HasClaws | // Compiles, as expected AnimalFlags::Endangered; // int eagle = AnimalFlags::CanFly | // Won't compile, as expected // AnimalFlags::HasClaws; // int has_claws = seahawk & AnimalFlags::CanFly; // Won't compile, as expected if (seahawk & AnimalFlags::CanFly) {} // Compiles, as expected seahawk = seahawk & AnimalFlags::CanFly; // Compiles, as expected return 0; }
这将创build必要的操作符的重载,但使用SFINAE将它们限制为枚举types。 请注意,为了简洁起见,我没有定义所有的操作符,但唯一不同的是&
。 运算符目前是全局的(即适用于所有枚举types),但是可以通过将重载放在名称空间(我所做的)中,或通过添加额外的SFINAE条件(可能使用特定的基础types或专门创build的types别名)。 underlying_type_t
是一个C ++ 14的特性,但是它似乎被很好的支持,很容易用C ++ 11来模拟一个简单的template<typename T> using underlying_type_t = underlying_type<T>::type;
如果你的编译器还不支持强types枚举,你可以看一下c ++源代码中的以下文章 :
摘要:
本文提出了一个解决约束位操作的问题
只允许安全和合法的操作,并将所有无效位操作转换为编译时错误。 最重要的是,位操作的语法保持不变,并且使用位的代码不需要修改,除了可能修复仍然未被检测到的错误之外。
我发现自己问同样的问题,并提出了一个通用的基于C ++ 11的解决scheme,类似于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 ++标准明确地谈到了这一点,请参见“17.5.2.1.3位掩码types”一节:
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类的枚举成员,则需要使用跟踪引用:
AnimalFlags% operator|=(AnimalFlags% X, AnimalFlags Y) { X = X | Y; return X; }
你混淆了对象和对象的集合。 具体来说,你正在混淆二进制标志与二进制标志集合。 一个适当的解决scheme将如下所示:
// These are individual flags enum AnimalFlag // Flag, not Flags { HasClaws = 0, CanFly, EatsFish, Endangered }; class AnimalFlagSet { int m_Flags; public: AnimalFlagSet() : m_Flags(0) { } void Set( AnimalFlag flag ) { m_Flags |= (1 << flag); } void Clear( AnimalFlag flag ) { m_Flags &= ~ (1 << flag); } bool Get( AnimalFlag flag ) const { return (m_Flags >> flag) & 1; } };
如上所述(Kai)或执行以下操作。 真正的枚举是“枚举”,你想要做的是有一个集合,所以你应该真的使用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); }
如果你实际上没有使用单独的枚举值(例如,你不需要closures它们),并且如果你不担心维护二进制兼容性,比如:不关心你的位置在哪里…你可能是。 另外你最好不要太在意范围和访问控制。 嗯,枚举有一些不错的属性位域…不知有没有人试过:)
struct AnimalProperties { bool HasClaws : 1; bool CanFly : 1; bool EatsFish : 1; bool Endangered : 1; }; union AnimalDescription { AnimalProperties Properties; int Flags; }; void TestUnionFlags() { AnimalDescription propertiesA; propertiesA.Properties.CanFly = true; AnimalDescription propertiesB = propertiesA; propertiesB.Properties.EatsFish = true; if( propertiesA.Flags == propertiesB.Flags ) { cout << "Life is terrible :("; } else { cout << "Life is great!"; } AnimalDescription propertiesC = propertiesA; if( propertiesA.Flags == propertiesC.Flags ) { cout << "Life is great!"; } else { cout << "Life is terrible :("; } }
我们可以看到,生活是伟大的,我们有我们的离散价值观,我们有一个很好的诠释&和| 对我们的内心而言,内容仍然有其意义。 一切都是一致的和可预测的…对我来说,只要我继续使用微软的VC ++编译器W /更新3 Win10的X64,不要碰我的编译器标志:)
即使一切都很好…我们现在有一些关于国旗的含义的背景,因为它在一个工会w /在可怕的现实世界中的位场,你的程序可能会负责多个单独的任务,你可以仍然意外地(很容易地)把不同的工会的两个标志域一起粉碎(比如AnimalProperties和ObjectProperties,因为它们都是整数),把你所有的位混合在一起,这是一个可怕的错误追踪…我怎么知道很多人在这个post上不经常使用bitmasks,因为构build它们很容易,维护它们很困难。
class AnimalDefinition { public: static AnimalDefinition *GetAnimalDefinition( AnimalFlags flags ); //A little too obvious for my taste... NEXT! static AnimalDefinition *GetAnimalDefinition( AnimalProperties properties ); //Oh I see how to use this! BORING, NEXT! static AnimalDefinition *GetAnimalDefinition( int flags ); //hmm, wish I could see how to construct a valid "flags" int without CrossFingers+Ctrl+Shift+F("Animal*"). Maybe just hard-code 16 or something? AnimalFlags animalFlags; //Well this is *way* too hard to break unintentionally, screw this! int flags; //PERFECT! Nothing will ever go wrong here... //wait, what values are used for this particular flags field? Is this AnimalFlags or ObjectFlags? Or is it RuntimePlatformFlags? Does it matter? Where's the documentation? //Well luckily anyone in the code base and get confused and destroy the whole program! At least I don't need to static_cast anymore, phew! private: AnimalDescription m_description; //Oh I know what this is. All of the mystery and excitement of life has been stolen away :( }
所以你把你的union声明设为private,以防止直接访问“Flags”,并且必须添加getters / setter和operator overload,然后创build一个macros,而且你基本上就回到你开始的时候,用Enum做这个。
不幸的是,如果你希望你的代码是可移植的,我不认为有什么办法来保证位布局,或者B)在编译时确定位布局(所以你可以跟踪它,版本/平台等) 偏移在一个结构与位域
在运行时,你可以玩技巧,设置字段和异或标志,看看哪些位改变了,听起来很蹩脚,虽然有100%一致的,平台独立,完全确定的解决scheme,即ENUM。
TL; DR:不要听仇恨。 C ++不是英文的。 只是因为从Cinheritance的缩写关键字的字面定义可能不适合您的使用,并不意味着当关键字的C 和 C ++定义绝对包含您的用例时,您不应该使用它。 你也可以使用结构来模拟结构以外的东西,除了学校和社会阶层以外的东西。 你可以使用浮点数来接地。 你可以使用char来表示既不是未燃烧的variables,也不是小说,游戏或电影中的人物。 任何程序员谁去字典,以确定一个关键字的含义之前,语言规范是…好吧,我会把我的舌头在那里。
如果你确实希望你的代码build立在口语之后,那么最好用Objective-C编写,而Objective-C偶尔也会在位域中使用枚举。
我想详细说明Uliwitness的答案 ,修复C ++ 98的代码,并使用Safe Bool成语 ,缺lessstd::underlying_type<>
模板和C ++ 11下C ++版本中的explicit
关键字。
我也修改它,以便枚举值可以顺序没有任何明确的任务,所以你可以有
enum AnimalFlags_ { HasClaws, CanFly, EatsFish, Endangered }; typedef FlagsEnum<AnimalFlags_> AnimalFlags; seahawk.flags = AnimalFlags() | CanFly | EatsFish | Endangered;
然后你可以得到原始的标志值
seahawk.flags.value();
这是代码。
template <typename EnumType, typename Underlying = int> class FlagsEnum { typedef Underlying FlagsEnum::* RestrictedBool; public: FlagsEnum() : m_flags(Underlying()) {} FlagsEnum(EnumType singleFlag): m_flags(1 << singleFlag) {} FlagsEnum(const FlagsEnum& original): m_flags(original.m_flags) {} FlagsEnum& operator |=(const FlagsEnum& f) { m_flags |= f.m_flags; return *this; } FlagsEnum& operator &=(const FlagsEnum& f) { m_flags &= f.m_flags; return *this; } friend FlagsEnum operator |(const FlagsEnum& f1, const FlagsEnum& f2) { return FlagsEnum(f1) |= f2; } friend FlagsEnum operator &(const FlagsEnum& f1, const FlagsEnum& f2) { return FlagsEnum(f1) &= f2; } FlagsEnum operator ~() const { FlagsEnum result(*this); result.m_flags = ~result.m_flags; return result; } operator RestrictedBool() const { return m_flags ? &FlagsEnum::m_flags : 0; } Underlying value() const { return m_flags; } protected: Underlying m_flags; };
这是我的解决scheme,无需任何一堆的重载或铸造:
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); }
我认为没关系,因为我们确定(非强types)枚举和整数。
正如(更长)侧面说明,如果你
- 想要使用强types的枚举和
- 不需要重复摆弄你的旗帜
- 性能不是问题
我会想出这个:
#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初始值设定项列表和enum class
。