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++中标志位的几种实现方法的主要内容,如果未能解决你的问题,请参考以下文章

Laravel:如何在控制器的几种方法中重用代码片段

求斐波那契数列第n位的几种实现方式及性能对比(c#语言)

Python调用C++程序的几种方法

a标签调用js的几种方法

Flags_API Ver2.0

一张图帮你记忆,Spring Boot 应用在启动阶段执行代码的几种方式