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); // Flag::Read is implicitly converted to `int`, losing type safety
f(Plain::Read); // Plain::Read is also implicitly converted to `int`
auto state = Flag::Read | Flag::Write; // type is not `Flag::State` as one could expect, it is `int` instead
g(state); // This function calls the `int` overload rather than the `Flag::State` overload
auto system1State = system1::getFlags();
auto system2State = system2::getFlags();
if (system1State == system2State) {} // Compiles properly, but semantics are broken, `Flag::State`
std::bitset<sizeof(Flag::State)> flagSet; // Notice that the type of bitset only indicates the amount of bits, there's no type safety here either
std::bitset<sizeof(Plain::State)> plainSet;
// f(flagSet); bitset doesn't implicitly convert to `int`, so this wouldn't compile which is slightly better than c-style `enum`
flagSet.set(Flag::Read); // No type safety, which means that bitset
flagSet.reset(Plain::Read); // is willing to accept values from any enumeration
h(flagSet); // Both kinds of sets can be
h(plainSet); // passed to the same function
}
尽管您可能认为这些问题在简单示例中很容易发现,但它们最终会蔓延到每个在 c 样式 enum 和 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); // Compile error, FlagState is not an `int`
f(PlainState::Read); // Compile error, PlainState is not an `int`
auto state = Flag::Read | Flag::Write; // type is `FlagState` as one could expect
g(state); // This function calls the `FlagState` overload
auto system1State = system1::getFlags();
auto system2State = system2::getFlags();
if (system1State == system2State) {} // Compile error, there is no `operator==(FlagState, PlainState)`
auto someFlag = h();
if (someFlag == FlagState::Read) {} // This compiles fine, but this is another type of recurring bug
}
此示例的最后一行显示了一个在编译时仍然无法捕获的问题。在某些情况下,比较平等可能是真正需要的。但大多数时候,真正的意思是if ((someFlag & FlagState::Read) == FlagState::Read)。
为了解决这个问题,我们必须区分枚举器的类型和位掩码的类型。这是一篇文章,详细介绍了我之前提到的部分解决方案的改进:https://dalzhim.github.io/2017/08/11/Improving-the-enum-class-bitmask/
免责声明:我是这篇文章的作者。
使用上一篇文章中的模板生成的按位运算符时,您将获得我们在上一段代码中展示的所有好处,同时还发现了mask == enumerator 错误。