C++ 类型安全枚举位标志
我希望为我的当前问题使用一组位标志。这些标志(很好地)定义为C++ 类型安全枚举位标志,c++,enums,bitwise-operators,c++03,C++,Enums,Bitwise Operators,C++03,我希望为我的当前问题使用一组位标志。这些标志(很好地)定义为枚举的一部分,但是我知道当您从一个枚举中选择或两个值时,或操作的返回类型为typeint 我目前正在寻找一种解决方案,它将允许位掩码的用户保持类型安全,因此我为operator| enum ENUM { ONE = 0x01, TWO = 0x02, THREE = 0x04, FOUR = 0x08, FIVE = 0x10, SIX = 0x20
枚举
的一部分,但是我知道当您从一个枚举中选择或
两个值时,或
操作的返回类型为typeint
我目前正在寻找一种解决方案,它将允许位掩码的用户保持类型安全,因此我为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;
}
enum枚举
{
一=0x01,
二=0x02,
三=0x04,
四=0x08,
5=0x10,
六=0x20
};
枚举运算符|(枚举lhs,枚举rhs)
{
//首先强制转换为int,否则我们只会递归
返回static_cast(static_cast(lhs)| static_cast(rhs));
}
无效枚举测试(枚举v)
{
}
int main(int argc,字符**argv)
{
//对enumTest的有效调用
抽样测试(一|二|五);
抽样测试(二|三|四|五);
枚举测试(一|二|三|四|五|六);
返回0;
}
这种过载真的提供了类型安全性吗?强制转换包含枚举中未定义的值的
int
是否会导致未定义的行为?有什么需要注意的吗?常量的值在或下未关闭。换句话说,一个或两个枚举常量的结果可能会产生一个非枚举常量的值:
0x30 == FIVE | SIX;
标准说这没问题,一个枚举可以有一个不等于它的任何枚举数(常量)的值。大概是为了允许这种类型的使用
在我看来,这不是类型安全的,因为如果要查看enumTest
的实现,您必须知道参数类型是ENUM
,但它的值可能不是ENUM
枚举数
我认为,如果这些只是位标志,那么就按照编译器的要求执行:使用
int
组合标志。和简单的enum
,例如您的:
enum ENUM
{
ONE = 0x01,
TWO = 0x02,
...
};
底层类型(很可能是int
)1是由实现定义的,但只要您要使用|
(按位或)创建掩码,结果将永远不需要比此枚举中最大值更宽的类型
[1]“枚举的基础类型是整数类型,可以表示枚举中定义的所有枚举数值。实现定义了使用哪个整数类型作为枚举的基础类型,但基础类型不得大于
int
,除非枚举数的值不能放入int
或无符号int
。“如果您考虑类型安全,最好使用
enum位{A,B,C,D};
std::位集bset,bset1;
b组(A);b组(C);
bset1[B]=1;
断言(bset[A]==bset[C]);
断言(B集[A]!=B集[B]);
断言(bset1!=bset);
这种过载真的提供了类型安全性吗
在这种情况下,是的。枚举的有效值范围至少达到(但不一定包括)最大命名枚举数后的下一个最大二次幂,以便允许它用于这样的位掩码。因此,对两个值的任何按位操作都将给出一个可由该类型表示的值
强制转换包含枚举中未定义的值的int是否会导致未定义的行为
不,只要这些值可以由枚举表示,它们就在这里
有什么需要注意的事项吗
如果您正在执行算术等操作,这可能会使值超出范围,那么您将得到一个实现定义的结果,但不是未定义的行为。这是我处理位标志的方法:
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)
然后,ONE+TWO
仍然是类型ENUM
。您可以重用该类来定义多个不同、不兼容类型的位标志集
我个人不喜欢使用
和&
来设置和测试位。设置和测试需要进行逻辑运算,但除非您考虑按位运算,否则它们不会表达运算的含义。如果你读出一、两个,你可能会认为你想要一个或两个,不一定两者都要。这就是为什么我更喜欢使用+
将标志添加到一起,并使用=
测试是否设置了标志
有关我建议的实现的更多详细信息,请参阅。
运算符|(五,六)==0x30
。什么枚举常量的值为0x30?我不会对结果值进行任何形式的比较,我只是检查标志。那么OR的结果应该保持为int。@Adam:为什么?OR的结果是此enumeration.BTW的有效值。Qt做了OP用模板尝试的事情QFlags你可能想看看。只要结果是一个有效的枚举值,强制转换就可以很好地定义,就像这里一样。有效值不仅是命名的枚举数,而且(至少)是二次幂的下一个最大值-它是这样指定的,以允许这种用法。@MikeSeymour您对此有参考吗?我发现的所有东西都说这是非法的。第7段是最相关的,因为它定义了值的范围。@MikeSeymour OP专门询问C++03。@MikeSeymour我想你指的是这句话:“可以定义一个枚举,它的值不是由任何枚举器定义的。”by“只要值可以由枚举表示”,您的意思是这些值必须由枚举的基础类型表示吗?
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);