我希望为当前的问题使用一组位标志。这些标志(很好地(被定义为enum
的一部分,但是我知道当您从枚举中OR
两个值时,OR
操作的返回类型具有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;
}
这种重载真的提供类型安全吗?强制转换包含枚举中未定义的值的int
是否会导致未定义的行为?有什么需要注意的吗?
这种重载真的提供类型安全吗?
在这种情况下,是的。枚举的有效值范围至少达到(但不一定包括(最大命名枚举器之后的下一个最大 2 的幂,以便允许将其用于此类位掩码。因此,对两个值的任何按位运算都将给出可由此类型表示的值。
强制转换包含枚举中未定义的值的 int 是否会导致未定义的行为?
否,只要值可由枚举表示,它们就在此处。
有什么需要注意的吗?
如果您正在执行算术等操作,这可能会使值超出范围,那么您将获得实现定义的结果,但不会获得未定义的行为。
如果你考虑类型安全,最好使用 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);
常量的值在 OR 下不闭合。换句话说,两个枚举常量的 OR 的结果可能会导致一个不是 ENUM 常量的值:
0x30 == FIVE | SIX;
该标准说这是可以的,一个 enumaration 的值可以不等于它的任何 enumarator(常量(。大概是为了允许这种类型的用法。
在我看来,这不是类型安全的,因为如果您要查看enumTest
的实现,您必须意识到参数类型是ENUM
,但它的值可能不是ENUM
枚举器。
我认为,如果这些只是位标志,那么按照编译器的要求去做:使用int
来组合标志。
您这样的简单enum
:
enum ENUM
{
ONE = 0x01,
TWO = 0x02,
...
};
它是实现定义的基础类型(很可能是int
(1,但只要要使用|
(按位或(来创建掩码,结果就永远不会需要比此枚举的最大值更宽的类型。
[1]"枚举的基础类型是一个整数类型,可以表示枚举中定义的所有枚举器值。它是实现定义的,哪个整型类型用作枚举的基础类型,但基础类型不得大于int
,除非枚举器的值无法容纳在int
或unsigned 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)
您可以像这样使用它:
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
你可能会认为你想要一个或两个,不一定同时需要两个。这就是为什么我更喜欢使用 +
将标志添加在一起,==
来测试是否设置了标志。
有关我建议的实现的更多详细信息,请参阅此博客文章。