C 枚举标志与位集

新手上路,请多包涵

枚举标志上使用位集的优点/缺点是什么?

 namespace Flag {
    enum State {
        Read   = 1 << 0,
        Write  = 1 << 1,
        Binary = 1 << 2,
    };
}

namespace Plain {
    enum State {
        Read,
        Write,
        Binary,
        Count
    };
}

int main()
{
    {
        unsigned int state = Flag::Read | Flag::Binary;
        std::cout << state << std::endl;

        state |= Flag::Write;
        state &= ~(Flag::Read | Flag::Binary);
        std::cout << state << std::endl;
    } {
        std::bitset<Plain::Count> state;
        state.set(Plain::Read);
        state.set(Plain::Binary);
        std::cout << state.to_ulong() << std::endl;

        state.flip();
        std::cout << state.to_ulong() << std::endl;
    }

    return 0;
}

到目前为止,正如我所看到的,位集有更方便的设置/清除/翻转功能来处理,但枚举标志的使用是一种更广泛使用的方法。

bitset 有哪些可能的缺点,我应该在日常代码中使用什么以及何时使用?

原文由 Nikolai Shalakin 发布,翻译遵循 CC BY-SA 4.0 许可协议

阅读 928
2 个回答

你编译优化了吗?几乎不可能有 24 倍的速度因子。

对我来说,bitset 更好,因为它为你管理空间:

  • 可以根据需要扩展。如果您有很多标志,您可能会在 int / long long 版本中用完空间。
  • 如果您只使用几个标志,可能会占用更少的空间(它可以适合 unsigned char / unsigned short - 我不确定实现是否应用了这种优化)

原文由 geza 发布,翻译遵循 CC BY-SA 3.0 许可协议

std::bitset 和 c-style 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 风格 enumstd::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 = FlagState::Read | FlagState::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 错误。

原文由 Dalzhim 发布,翻译遵循 CC BY-SA 4.0 许可协议

撰写回答
你尚未登录,登录后可以
  • 和开发者交流问题的细节
  • 关注并接收问题和回答的更新提醒
  • 参与内容的编辑和改进,让解决方法与时俱进
推荐问题