我想在C ++中创建一个方便的标志处理程序。
通常的想法是使用这样的东西:
enum class Flag {
Foo = 1<<0,
Bar = 1<<1,
Baz = 1<<2
};
// these operators can be generalized by using template functions with SFINAE
Flag operator|(Flag a, Flag b) {
return static_cast<Flag>(static_cast<underlying_type>(a)|static_cast<underlying_type>(b));
}
// add other operators here, like &, ^, |=, etc.
这有效,但我有两个问题:
Flag
的使用方式不一定是一个标志,而是几个标志。这不是一个大问题,但我不认为这个好设计,因为Flag
现在实际上存储了一个未列为枚举成员的值。如果我将运算符的返回值更改为underlying_type,那么我会丢失有关它存储Flag
此处理程序的另一个要求是不仅能够处理1位标志,而且能够处理几位宽的枚举(使用这些,很容易达到64位限制),我想要控制如何在处理程序之间分配位(我的意思是,例如,我想使用此处理程序来简化处理OS调用,其中位位置由API确定)。
这个问题有哪些(方便使用,最佳,一般)解决方案?
答案 0 :(得分:1)
只需使用std::vector<bool>
将标志存储为位(以这种方式实现)。如果位域的大小是固定的 - 使用std::bitset
。
答案 1 :(得分:1)
这就是我想出的。这只是一个概念,缺少许多功能(多位标志,其他运算符等),但可以轻松添加。
以下是一个使用示例:
enum class Group1 {
AttrA,
AttrB,
AttrC,
};
// count tells to Flags how many entries are there.
// no other boilerplate code is needed
constexpr int count(Group1) {
return 3;
}
enum class Group2 {
AttrA,
AttrB,
};
constexpr int count(Group2) {
return 2;
}
int main() {
Flags<Group1, Group2> flags; // flags will have 5 used bits in it
flags |= Group1::AttrA;
flags |= Group1::AttrC;
flags = Group2::AttrB|Group1::AttrA;
}
以下是解决方案:
#include <type_traits>
#include <cstdint>
namespace Private {
template <typename ...FLAGS>
struct FlagsSize;
template <typename FIRST, typename ...TAIL>
struct FlagsSize<FIRST, TAIL...> {
static constexpr int value = count(FIRST()) + FlagsSize<TAIL...>::value;
};
template <>
struct FlagsSize<> {
static constexpr int value = 0;
};
template <typename ELEMENT, typename ...FLAGS>
struct FlagOffset;
template <typename ELEMENT, typename ...FLAGS>
struct FlagOffset<ELEMENT, ELEMENT, FLAGS...> {
static constexpr int value = 0;
};
template <typename ELEMENT, typename FIRST, typename ...TAIL>
struct FlagOffset<ELEMENT, FIRST, TAIL...> {
static constexpr int value = count(FIRST()) + FlagOffset<ELEMENT, TAIL...>::value;
};
}
template <typename ...FLAGS>
class Flags {
public:
static const std::size_t size = Private::FlagsSize<FLAGS...>::value;
private:
std::uint8_t m_storage[(size+7)/8];
public:
Flags() {
clearAll();
}
void clearAll() {
for (std::size_t i=0; i<sizeof(m_storage); i++) {
m_storage[i] = 0;
}
}
bool get(int index) const {
return m_storage[index>>3]&(1<<(index&7));
}
void set(int index, bool value = true) {
if (value) {
m_storage[index>>3] |= 1<<(index&7);
} else {
m_storage[index>>3] &= ~(1<<(index&7));
}
}
template <typename FLAG, typename = std::enable_if_t<std::is_enum_v<FLAG>>>
bool get(FLAG flag) const {
return get(Private::FlagOffset<FLAG, FLAGS...>::value+static_cast<std::underlying_type_t<FLAG>>(flag));
}
template <typename FLAG, typename = std::enable_if_t<std::is_enum_v<FLAG>>>
Flags<FLAG> get() const {
Flags<FLAG> r;
for (std::size_t i=0; i<count(FLAG()); i++) {
r.set(static_cast<FLAG>(i), get(static_cast<FLAG>(i)));
}
return r;
}
template <typename FLAG, typename = std::enable_if_t<std::is_enum_v<FLAG>>>
void set(FLAG flag, bool value) {
set(Private::FlagOffset<FLAG, FLAGS...>::value+static_cast<std::underlying_type_t<FLAG>>(flag), value);
}
template <typename FLAG, typename = std::enable_if_t<std::is_enum_v<FLAG>>>
Flags &operator|=(FLAG flag) {
set(flag, true);
return *this;
}
template <typename FLAG, typename = std::enable_if_t<std::is_enum_v<FLAG>>>
Flags &operator|=(const Flags<FLAG> &flags) {
for (std::size_t i=0; i<count(FLAG()); i++) {
set(static_cast<FLAG>(i), flags.get(static_cast<FLAG>(i)));
}
return *this;
}
template <typename ...FLAG>
Flags &operator=(const Flags<FLAG...> &flags) {
clearAll();
(operator|=(flags.template get<FLAG>()), ...);
return *this;
}
};
template <typename FLAG, typename = std::enable_if_t<std::is_enum_v<FLAG>>>
constexpr Flags<FLAG> operator|(FLAG a, FLAG b) {
Flags<FLAG> f;
f |= a;
f |= b;
return f;
}
template <typename FLAGA, typename FLAGB, typename = std::enable_if_t<std::is_enum_v<FLAGA>&&std::is_enum_v<FLAGB>>>
constexpr Flags<FLAGA, FLAGB> operator|(FLAGA a, FLAGB b) {
Flags<FLAGA, FLAGB> f;
f |= a;
f |= b;
return f;
}