无论是std::bitset还是c风格的enum
都有管理标志的重要缺点。首先,让我们考虑下面的示例代码:
namespace Flag {
enum State {
Read = 1 << 0,
Write = 1 << 1,
Binary = 1 << 2,
};
}
namespace Plain {
enum State {
Read,
Write,
Binary,
Count
};
}
void f(int);
void g(int);
void g(Flag::State);
void h(std::bitset<sizeof(Flag::State)>);
namespace system1 {
Flag::State getFlags();
}
namespace system2 {
Plain::State getFlags();
}
int main()
{
f(Flag::Read);
f(Plain::Read);
auto state = Flag::Read | Flag::Write;
g(state);
auto system1State = system1::getFlags();
auto system2State = system2::getFlags();
if (system1State == system2State) {}
std::bitset<sizeof(Flag::State)> flagSet;
std::bitset<sizeof(Plain::State)> plainSet;
flagSet.set(Flag::Read);
flagSet.reset(Plain::Read);
h(flagSet);
h(plainSet);
}
即使你认为在简单的示例中很容易发现这些问题,但它们最终会潜伏在每个在 C 风格的枚举和
std::bitset 之上构建标志的代码库中。
那么,为了更好的类型安全性,你可以做什么呢?首先,C++11 的作用域枚举是一种改进的类型安全性方式。但它会大大阻碍便利性。解决方案的一部分是使用模板生成的按位运算符来处理作用域枚举。以下是一篇非常好的博客文章,它解释了如何使用并提供了工作代码
https://www.justsoftwaresolutions.co.uk/cplusplus/using-enum-classes-as-bitfields.html
现在让我们看看这会是什么样子:
enum class FlagState {
Read = 1 << 0,
Write = 1 << 1,
Binary = 1 << 2,
};
template<>
struct enable_bitmask_operators<FlagState>{
static const bool enable=true;
};
enum class PlainState {
Read,
Write,
Binary,
Count
};
void f(int);
void g(int);
void g(FlagState);
FlagState h();
namespace system1 {
FlagState getFlags();
}
namespace system2 {
PlainState getFlags();
}
int main()
{
f(FlagState::Read);
f(PlainState::Read);
auto state = FlagState::Read | FlagState::Write;
g(state);
auto system1State = system1::getFlags();
auto system2State = system2::getFlags();
if (system1State == system2State) {}
auto someFlag = h();
if (someFlag == FlagState::Read) {}
}
这个例子的最后一行显示了一个仍然无法在编译时捕获的问题。在某些情况下,比较相等可能是真正需要的。但大多数时候,真正意思是
if ((someFlag & FlagState::Read) == FlagState::Read)
。
为了解决这个问题,我们必须区分枚举类型和位掩码类型。以下是一篇文章,详细介绍了我之前提到的部分解决方案的改进:
https://dalzhim.github.io/2017/08/11/Improving-the-enum-class-bitmask/
免责声明:我是这篇后来的文章的作者。
当使用上一篇文章中生成的模板位运算符时,您将获得我们在上一段代码中演示的所有好处,同时还可以捕获
mask == enumerator
的错误。