C++ C++;枚举标志与位集

C++ C++;枚举标志与位集,c++,enums,bitset,C++,Enums,Bitset,使用位集优于枚举标志的优缺点是什么 namespace Flag { enum State { Read = 1 << 0, Write = 1 << 1, Binary = 1 << 2, }; } namespace Plain { enum State { Read, Write, Binary, Count

使用位集优于枚举标志的优缺点是什么

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;
}
名称空间标志{
枚举状态{

Read=1您是否在优化打开的情况下编译?不太可能有24倍的速度系数

对我来说,位集更优越,因为它为您管理空间:

  • 可以根据需要进行扩展。如果有很多标志,则
    int
    /
    long
    版本中的空间可能会不足
  • 如果只使用几个标志,可能会占用更少的空间(它可以放入
    无符号字符
    /
    无符号短字符
    ——但我不确定实现是否应用了此优化)
(广告模式开启) 你可以同时得到:一个方便的界面和最大的性能。以及类型安全性。

< P>和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
}
本例的最后一行显示了一个在编译时仍然无法捕获的问题。在某些情况下,比较是否相等可能是真正需要的。但大多数情况下,真正需要的是
如果((someFlag&FlagState::Read)=FlagState::Read)

为了解决这个问题,我们必须区分枚举数的类型和位掩码的类型。下面有一篇文章详细介绍了我前面提到的部分解决方案的改进: 免责声明:我是后面这篇文章的作者

当使用上一篇文章中模板生成的按位运算符时,您将获得我们在最后一段代码中演示的所有好处,同时还将捕获
掩码==enumerator
错误。

一些观察结果:

  • std::bitset
    支持任意位数(例如,超过64位),而枚举的基本整数类型限制为64位
  • std::bitset
    可以隐式(取决于
    std
    实现)使用符合请求位数的最小大小的基础整数类型,而枚举的基础整数类型需要显式声明(否则,
    int
    将用作默认的基础整数类型))
  • std::bitset
    表示N位的通用序列,而作用域枚举提供类型安全性,可用于方法重载
  • 如果
    std::bitset
    用作位掩码,则典型的实现取决于用于索引(!=掩码)目的的附加枚举类型
请注意,为了方便起见,后两个观察值可以结合起来定义强
std::bitset
类型:

typename< Enum E, std::size_t N >
class BitSet : public std::bitset< N >
{
    ...

    [[nodiscard]]
    constexpr bool operator[](E pos) const;

    ...
};
typename
类位集:公共标准::位集
{
...
[[nodiscard]]
constexpr布尔运算符[](E pos)const;
...
};
如果代码支持某种反射以获得显式枚举值的数量,那么可以直接从枚举类型推断位的数量

  • 作用域枚举类型没有按位运算符重载(使用SFINAE或所有作用域和非作用域枚举类型的概念很容易定义,但在使用之前需要包含),未作用域枚举类型将衰减为基础整数类型
  • 枚举类型的按位运算符重载需要的样板文件少于
    std::bitset
    (例如,
    auto flags=Depth | Stencil;
  • 枚举类型支持有符号和无符号的基础整数类型,而
    std::bitset
    在内部使用无符号整数类型(移位运算符)


FWIIW,在我自己的代码中,我主要使用
std::bitset
(和
eastl::bitvector
)作为设置/获取单个位的
private
bit/
bool
containers/
bool
s。对于掩蔽操作,我更喜欢具有显式定义的底层类型和按位运算符重载的作用域枚举类型。

由于标志是预先计算的,因此它们在测试中具有明显的优势。我认为这一切都取决于这取决于用例、个人喜好、项目需求、代码风格指南,以及更多。如果是针对你自己的项目,那么就去做你觉得最好的事情。我的建议是,在性能之前先考虑可读性、可维护性和正确性。“足够好”。通常已经足够好了。位集可以与constexpr一起工作吗?您可能会得到相同的计时。但是,一般来说,位集速度较慢,因为它具有平台不可知的特性。
位集速度明显较慢(在我的机器上约为24倍)与裸位操作相比
我有另一个结果几乎与代码一样快。首先:这两个示例不等效!在显式翻转后,必须设置读取和二进制标志才能真正获得等效性。因此,实际上,位集变量生成的代码更长(四行)…当然,并非总是较短的代码更易于阅读。对我来说,由于我非常习惯裸位操作,因此它与位集变体一样易于阅读,因此,我更喜欢前者,但这是一个非常私人的问题…这里有一个不同的选项:
typename< Enum E, std::size_t N >
class BitSet : public std::bitset< N >
{
    ...

    [[nodiscard]]
    constexpr bool operator[](E pos) const;

    ...
};