我希望为我当前的问题使用一组位标志。这些标志(很好地)被定义为enum
的一部分,但是我理解当你OR
来自枚举的两个值时,OR
操作的返回类型具有类型{{1} }。
我目前正在寻找的是一种解决方案,它允许位掩码的用户保持类型安全,因此我为int
创建了以下重载
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;
}
值会导致未定义的行为吗?有什么需要注意的注意事项吗?
答案 0 :(得分:6)
这种过载真的能提供类型安全吗?
在这种情况下,是的。枚举的有效值范围至少达到(但不一定包括)最大命名枚举器之后的下一个最大2的幂,以便允许它用于这样的位掩码。因此,对两个值的任何按位运算都会给出一个可由此类型表示的值。
转换包含未在枚举中定义的值的int会导致未定义的行为吗?
不,只要这些值可由枚举表示,它们就在这里。
有什么需要注意的注意事项吗?
如果您正在执行诸如算术之类的操作,这可能会使值超出范围,那么您将获得实现定义的结果,但不是未定义的行为。
答案 1 :(得分:4)
如果您考虑类型安全,最好使用std::bitset
enum BITS { A, B, C, D };
std::bitset<4> bset, bset1;
bset.set(A); bset.set(C);
bset1[B] = 1;
assert(bset[A] == bset[C]);
assert(bset[A] != bset[B]);
assert(bset1 != bset);
答案 2 :(得分:3)
在OR下不会关闭常量的值。换句话说,两个ENUM常量的OR结果可能会产生一个不是ENUM常量的值:
0x30 == FIVE | SIX;
标准说这没关系,一个咒语可以有一个不等于任何一个煽动者(常数)的值。大概是允许这种用法。
在我看来,这不是类型安全的,因为如果你要查看enumTest
的实现,你必须知道参数类型是ENUM
,但它可能有一个不是一个值ENUM
枚举。
我认为如果这些只是位标志,那么就按照编译器的要求进行操作:使用int
作为标志组合。
答案 3 :(得分:2)
使用简单的enum
,例如你的:
enum ENUM
{
ONE = 0x01,
TWO = 0x02,
...
};
它是实现定义的底层类型(最有可能是int
) 1 ,但只要您打算使用|
(按位或)来创建掩码,结果永远不会需要比此枚举中最大值更宽的类型。
[1] “枚举的基础类型是一个整数类型,可以表示枚举中定义的所有枚举器值。它是实现定义的哪个整数类型使用作为枚举的基础类型,但基础类型不得大于int
,除非枚举值不适合int
或{{1 }} 强>“。
答案 4 :(得分:1)
这是我对位标志的处理方法:
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);
然后ONE + TWO
仍为ENUM
类型。并且您可以重新使用该类来定义具有不同,不兼容类型的多个位标志集。
我个人不喜欢使用|
和&
来设置和测试位。这是设置和测试需要完成的逻辑操作,但除非您考虑按位操作,否则它们不表示操作的含义。如果你读出ONE | TWO
,你可能认为你想要一个或两个,不一定两个。这就是为什么我更喜欢使用+
一起添加标记和==
来测试是否设置了标记。
有关我建议的实施的详细信息,请参阅this blog post。