将enums作为标志处理可以在C#中通过[Flags]属性很好地工作,但是在C++中执行此操作的最佳方法是什么?
例如,我想写:
enum AnimalFlags
{
HasClaws = 1,
CanFly =2,
EatsFish = 4,
Endangered = 8
};
seahawk.flags = CanFly | EatsFish | Endangered;
Run Code Online (Sandbox Code Playgroud)
但是,我收到关于int/ enum转换的编译器错误.是否有更好的表达方式而不仅仅是直接的铸造?优选地,我不想依赖来自第三方库的构造,例如boost或Qt.
编辑:如答案中所示,我可以通过声明seahawk.flags为避免编译器错误int.但是,我想有一些机制来强制执行类型安全,所以有人不能写seahawk.flags = HasMaximizeButton.
小智 230
"正确"的方法是为枚举定义位运算符,如:
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));
}
Run Code Online (Sandbox Code Playgroud)
等等其他位运算符.如果枚举范围超出int范围,则根据需要进行修改.
Wou*_*rvD 119
注意(也有点偏离主题):使用位移可以完成另一种制作唯一标志的方法.我,我自己,发现这更容易阅读.
enum Flags
{
A = 1 << 0, // binary 0001
B = 1 << 1, // binary 0010
C = 1 << 2, // binary 0100
D = 1 << 3, // binary 1000
};
Run Code Online (Sandbox Code Playgroud)
它可以将值保持为int,因此在大多数情况下,32个标志清楚地反映在移位量中.
use*_*594 51
对于像我这样的懒人,这里是复制和粘贴的模板化解决方案:
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); }
Run Code Online (Sandbox Code Playgroud)
Bri*_*ndy 42
seahawk.flags变量是什么类型的?
在标准C++中,枚举不是类型安全的.它们实际上是整数.
AnimalFlags不应该是变量的类型,你的变量应该是int,错误就会消失.
像其他人建议的那样放置十六进制值是不需要的,它没有任何区别.
默认情况下,枚举值为int类型.所以你肯定可以按位或组合它们并将它们放在一起并将结果存储在int中.
枚举类型是int的受限子集,其值是其枚举值之一.因此,当您在该范围之外创建一些新值时,如果不转换为枚举类型的变量,则无法分配它.
如果您愿意,也可以更改枚举值类型,但这个问题没有意义.
编辑:海报说他们关心类型安全,他们不希望int类型中不应存在的值.
但是将AnimalFlags范围之外的值放在AnimalFlags类型的变量中是不安全的.
虽然在int类型中有一种安全的方法可以检查超出范围的值...
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
};
Run Code Online (Sandbox Code Playgroud)
以上并不会阻止您从具有值1,2,4或8的不同枚举中放入无效标志.
如果你想要绝对类型安全,那么你可以简单地创建一个std :: set并将每个标志存储在那里.它不是空间有效的,但它是类型安全的,并提供与bitflag int相同的能力.
C++ 0x note:强类型枚举
在C++ 0x中,您最终可以拥有类型安全的枚举值....
enum class AnimalFlags {
CanFly = 2,
HasClaws = 4
};
if(CanFly == 2) { }//Compiling error
Run Code Online (Sandbox Code Playgroud)
Sim*_*ier 39
请注意,如果您在Windows环境中工作,则DEFINE_ENUM_FLAG_OPERATORS在winnt.h中定义了一个为您完成工作的宏.所以在这种情况下,你可以这样做:
enum AnimalFlags
{
HasClaws = 1,
CanFly =2,
EatsFish = 4,
Endangered = 8
};
DEFINE_ENUM_FLAG_OPERATORS(AnimalFlags)
seahawk.flags = CanFly | EatsFish | Endangered;
Run Code Online (Sandbox Code Playgroud)
uli*_*ess 25
我发现eidolon目前接受的答案太危险了.编译器的优化器可能会对枚举中的可能值进行假设,并且您可能会使用无效值返回垃圾.通常没有人想要在标志枚举中定义所有可能的排列.
正如Brian R. Bondy在下面所述,如果您正在使用C++ 11(每个人都应该这样,它就是那么好),您现在可以更轻松地使用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.
Run Code Online (Sandbox Code Playgroud)
这通过指定枚举的类型来确保稳定的大小和值范围,通过使用禁止自动向下注入枚举等enum class,并且用于constexpr确保运算符的代码被内联并因此与常规数字一样快.
对于那些坚持使用11前C++方言的人
如果我遇到了不支持C++ 11的编译器,我会在一个类中包装一个int类型,然后只允许使用按位运算符和该枚举中的类型来设置它的值:
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;
};
Run Code Online (Sandbox Code Playgroud)
您可以将其定义为常规枚举+ typedef:
enum TFlags_
{
EFlagsNone = 0,
EFlagOne = (1 << 0),
EFlagTwo = (1 << 1),
EFlagThree = (1 << 2),
EFlagFour = (1 << 3)
};
typedef SafeEnum<enum TFlags_> TFlags;
Run Code Online (Sandbox Code Playgroud)
用法也类似:
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;
Run Code Online (Sandbox Code Playgroud)
您还可以enum foo : type使用第二个模板参数覆盖二进制稳定枚举的基础类型(如C++ 11 ),即typedef SafeEnum<enum TFlags_,uint8_t> TFlags;.
我operator bool使用C++ 11的explicit关键字标记了覆盖,以防止它导致int转换,因为这些可能导致标记集在写出时最终折叠为0或1.如果你不能使用C++ 11,请保留该重载并重写示例用法中的第一个条件(myFlags & EFlagTwo) == EFlagTwo.
sor*_*oru 18
最简单的方法来做到这一点,如图在这里,使用标准库类的bitset.
要以类型安全的方式模拟C#功能,您必须在bitset周围编写一个模板包装器,将int参数替换为作为模板的类型参数给出的枚举.就像是:
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;
Run Code Online (Sandbox Code Playgroud)
Tre*_*vor 11
在我看来,到目前为止,没有一个答案是理想的.为了理想我会期望解决方案:
==,!=,=,&,&=,|,|=和~在传统意义上运营商(即,a & b)if (a & b)...到目前为止,大多数解决方案都落在第2点或第3点.在我看来,WebDancer是关闭但在第3点失败,需要为每个枚举重复.
我提出的解决方案是WebDancer的通用版本,它也解决了第3点:
#include <cstdint>
#include <type_traits>
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;
}
Run Code Online (Sandbox Code Playgroud)
这会创建必要运算符的重载,但使用SFINAE将它们限制为枚举类型.请注意,为了简洁起见,我没有定义所有的运算符,但唯一不同的是运算符&.该运营商目前正在全球(即适用于所有枚举类型),但这可能通过将超载的命名空间(我做什么),或通过增加额外的SFINAE条件被降低或者(可能使用特定的底层类型,或专门设立的类型别名).这underlying_type_t是一个C++ 14的功能,但它似乎得到很好的支持,很容易用简单的C++ 11模拟template<typename T> using underlying_type_t = underlying_type<T>::type;
小智 7
我使用以下宏:
#define ENUM_FLAG_OPERATORS(T) \
inline T operator~ (T a) { return static_cast<T>( ~static_cast<std::underlying_type<T>::type>(a) ); } \
inline T operator| (T a, T b) { return static_cast<T>( static_cast<std::underlying_type<T>::type>(a) | static_cast<std::underlying_type<T>::type>(b) ); } \
inline T operator& (T a, T b) { return static_cast<T>( static_cast<std::underlying_type<T>::type>(a) & static_cast<std::underlying_type<T>::type>(b) ); } \
inline T operator^ (T a, T b) { return static_cast<T>( static_cast<std::underlying_type<T>::type>(a) ^ static_cast<std::underlying_type<T>::type>(b) ); } \
inline T& operator|= (T& a, T b) { return reinterpret_cast<T&>( reinterpret_cast<std::underlying_type<T>::type&>(a) |= static_cast<std::underlying_type<T>::type>(b) ); } \
inline T& operator&= (T& a, T b) { return reinterpret_cast<T&>( reinterpret_cast<std::underlying_type<T>::type&>(a) &= static_cast<std::underlying_type<T>::type>(b) ); } \
inline T& operator^= (T& a, T b) { return reinterpret_cast<T&>( reinterpret_cast<std::underlying_type<T>::type&>(a) ^= static_cast<std::underlying_type<T>::type>(b) ); }
Run Code Online (Sandbox Code Playgroud)
它类似于上面提到的那些,但有几个改进:
int)它确实需要包含 type_traits:
#include <type_traits>
Run Code Online (Sandbox Code Playgroud)
我发现自己问了同样的问题,并提出了一个基于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);
}
};
Run Code Online (Sandbox Code Playgroud)
界面可以改善味道.然后就可以这样使用:
FlagSet<Flags> flags{Flags::FLAG_A, Flags::FLAG_C};
flags |= Flags::FLAG_D;
Run Code Online (Sandbox Code Playgroud)
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;
}
Run Code Online (Sandbox Code Playgroud)
和其他运营商类似.另请注意"constexpr",如果您希望编译器能够执行运算符编译时,则需要它.
如果您正在使用C++/CLI并希望能够分配给refum类的枚举成员,则需要使用跟踪引用:
AnimalFlags% operator|=(AnimalFlags% X, AnimalFlags Y) {
X = X | Y; return X;
}
Run Code Online (Sandbox Code Playgroud)
注意:此示例不完整,请参阅"17.5.2.1.3位掩码类型"部分以获取完整的运算符集.
只有语法糖。没有额外的元数据。
namespace UserRole // grupy
{
constexpr uint8_t dea = 1;
constexpr uint8_t red = 2;
constexpr uint8_t stu = 4;
constexpr uint8_t kie = 8;
constexpr uint8_t adm = 16;
constexpr uint8_t mas = 32;
}
Run Code Online (Sandbox Code Playgroud)
整数类型的标志运算符才有效。
| 归档时间: |
|
| 查看次数: |
133969 次 |
| 最近记录: |