C++中标志位的几种实现方法
Posted 软件工程师之路
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了C++中标志位的几种实现方法相关的知识,希望对你有一定的参考价值。
在软件设计以及日常开发中,标志位是非常常用的,而且也有多种实现方法,譬如:
-
宏定义与位操作(C++11后请采用 constexpr
) -
使用枚举 -
使用 std::bitset
-
使用位域
C++
语言在发展,宏定义能少用就少用,下面来看一下其它方法是如何实现的.
使用枚举
在Qt
中提供了QFlags
模板类,开发者需要定义枚举,然后用宏包裹,例如:
class MyClass
{
public:
enum Option {
NoOptions = 0x0,
ShowTabs = 0x1,
ShowAll = 0x2,
SqueezeBlank = 0x4
};
Q_DECLARE_FLAGS(Options, Option)
//...
};
Q_DECLARE_OPERATORS_FOR_FLAGS(MyClass::Options)
采用这种方式实现时,需要注意以下几点:
-
枚举值的定义不能随意处理,需要是 0x1
的按位左移 -
由于 enum
这种的枚举项默认全局可见,是不推荐使用的,但是把它包裹在类定义里刚好解决了这个问题
如果不能依赖于Qt
,也可以采用模板技术实现通用的,基于枚举的标志位,例如:
template<typename T, typename E = void>
class Flags;
template<typename T>
class Flags<T, std::enable_if_t<std::is_enum<T>::value>> {
unsigned long long v{};
public:
Flags() = default;
//从枚举列表构建
Flags(std::initializer_list<T> flags)
{
for (auto f : flags) {
v |= static_cast<unsigned long long>(f);
}
};
//判断标记是否设置
bool testFlag(T flag) const {
return v | static_cast<unsigned long long>(flag);
}
//设置标记位
Flags& setFlag(T flag, bool on = true) {
if (on) {
v |= static_cast<unsigned long long>(flag);
}
else
{
v &= ~static_cast<unsigned long long>(flag);
}
return *this;
}
//其它实现
};
使用方式类似如下:
//注意这里用struct包裹存粹是为了避免enum定义污染全局命名空间
struct Option {
enum OptionType{
NoOptions = 0x0,
ShowTabs = 0x1,
ShowAll = 0x2,
SqueezeBlank = 0x4
};
};
using Options = Flags<Option::OptionType>;
void example(){
Options options{Option::ShowTabs,Option::SqueezeBlank};
options.setFlag(Option::ShowAll);
}
上述实现有个最大的问题是对枚举定义有严格要求,不能随意定义,下面看以下如何利用std::bitset
从任何枚举类型定义出标志位.
使用std::bitset
在使用std::bitset
时,现有C++
标准并不能获取枚举的最大值,也就无从判断到底标志位要多大,这时可以强制要求开发者提供标志位的位数,其定义如下:
template<typename T,std::size_t N,typename E = void>
class Flags;
简单的实现示例为:
//模板参数E用来在开发者不使用枚举初始化时报错
template<typename T,std::size_t N>
class Flags<T, N, std::enable_if_t<std::is_enum<T>::value>> {
std::bitset<N> bits;
public:
Flags() = default;
Flags(std::initializer_list<T> flags)
{
for (auto f : flags) {
bits.set(static_cast<std::size_t>(f));
}
}
bool testFlag(T flag) const {
return bits.test(static_cast<std::size_t>(flag));
}
Flags& setFlag(T flag, bool on = true) {
bits.set(static_cast<std::size_t>(flag), on);
return *this;
}
};
开发者可以正常定义枚举:
struct Option {
enum OptionType {
ShowTabs,
ShowAll,
SqueezeBlank
};
};
然后指定枚举类型和位数定义标志位:
using Options = Flags<Option::OptionType, 4>;
就可以按照常规方式使用了:
void example() {
Options options{ Option::ShowTabs,Option::SqueezeBlank };
options.setFlag(Option::ShowAll);
if (options.testFlag(Option::ShowAll)) {
}
}
利用std::bitset
提供的丰富特性,可以实现相当多的支持,譬如序列化、保存与加载等等.
不过这些都需要定义枚举,如果枚举对于开发者并不重要,那么有没有不使用枚举的方法?
使用位域
实际上位域是实现标志位最简单的方式,上述的示例可以直接定义如下:
struct Options {
bool ShowTabs : 1;
bool ShowAll : 1;
bool SqueezeBlank : 1;
};
使用方式也简单直接:
void example() {
Options options{ true,false,true };
options.ShowAll = true;
if (options.ShowAll) {
}
}
不过这种方式没办法提供相对丰富的位操作,只能通过访问结构体成员来操作,而且内存布局依赖于编译器实现.
以上是关于C++中标志位的几种实现方法的主要内容,如果未能解决你的问题,请参考以下文章