Ane*_*dar 5 c++ flags bitflags
我目前正在尝试提出一种巧妙的方法来实现标志,除了通常的“true”和“false”之外,还包括状态“default”和(可选)“toggle”。
标志的一般问题是,它有一个函数并希望通过传递某些参数来定义其行为(“做某事”或“不做某事”)。
使用单个(布尔值)标志,解决方案很简单:
void foo(...,bool flag){
if(flag){/*do something*/}
}
Run Code Online (Sandbox Code Playgroud)
在这里添加默认值特别容易,只需将函数更改为
void foo(...,bool flag=true)
Run Code Online (Sandbox Code Playgroud)
并在没有标志参数的情况下调用它。
一旦标志数量增加,我通常看到和使用的解决方案是这样的:
typedef int Flag;
static const Flag Flag1 = 1<<0;
static const Flag Flag2 = 1<<1;
static const Flag Flag3 = 1<<2;
void foo(/*other arguments ,*/ Flag f){
if(f & Flag1){/*do whatever Flag1 indicates*/}
/*check other flags*/
}
//call like this:
foo(/*args ,*/ Flag1 | Flag3)
Run Code Online (Sandbox Code Playgroud)
这样做的好处是,您不需要每个标志的参数,这意味着用户可以设置他喜欢的标志,而只需忘记他不关心的标志。特别是你不会接到电话,比如foo (/*args*/, true, false, true)你必须计算哪个真/假表示哪个标志。
这里的问题是:
如果你设置了一个默认参数,一旦用户指定了任何标志,它就会被覆盖。不可能像Flag1=true, Flag2=false, Flag3=default.
显然,如果我们想要 3 个选项(真、假、默认),我们需要为每个标志至少传递 2 位。因此,虽然它可能不是必需的,但我想任何实现都应该很容易使用第 4 个状态来指示切换(= !默认)。
我有两种方法,但我对它们都不太满意:
到目前为止,我尝试使用这样的东西:
typedef int Flag;
static const Flag Flag1 = 1<<0;
static const Flag Flag1False= 1<<1;
static const Flag Flag1Toggle = Flag1 | Flag1False;
static const Flag Flag2= 1<<2;
static const Flag Flag2False= 1<<3;
static const Flag Flag2Toggle = Flag2 | Flag2False;
void applyDefault(Flag& f){
//do nothing for flags with default false
//for flags with default true:
f = ( f & Flag1False)? f & ~Flag1 : f | Flag1;
//if the false bit is set, it is either false or toggle, anyway: clear the bit
//if its not set, its either true or default, anyway: set
}
void foo(/*args ,*/ Flag f){
applyDefault(f);
if (f & Flag1) //do whatever Flag1 indicates
}
Run Code Online (Sandbox Code Playgroud)
但是,我不喜欢的是,每个标志使用两个不同的位。这导致“default-true”和“default-false”标志的不同代码以及必要的 if 而不是applyDefault().
通过定义这样的模板类:
struct Flag{
virtual bool apply(bool prev) const =0;
};
template<bool mTrue, bool mFalse>
struct TFlag: public Flag{
inline bool apply(bool prev) const{
return (!prev&&mTrue)||(prev&&!mFalse);
}
};
TFlag<true,false> fTrue;
TFlag<false,true> fFalse;
TFlag<false,false> fDefault;
TFlag<true,true> fToggle;
Run Code Online (Sandbox Code Playgroud)
我能够将其压缩apply为单个按位运算,在编译时除了 1 个参数之外的所有参数都是已知的。因此,使用TFlag::apply直接编译(使用gcc)在同一台机器代码为return true;,return false;,return prev;或return !prev;会,这是非常有效的,但是这意味着我必须使用模板的功能,如果我想传递一个TFlag作为参数。继承Flag和使用const Flag&as 参数会增加虚函数调用的开销,但可以避免使用模板。
但是我不知道如何将其扩展到多个标志......
所以问题是:如何在 C++ 中的单个参数中实现多个标志,以便用户可以轻松地将它们设置为“真”、“假”或“默认”(通过不设置特定标志)或(可选)表示“任何不是默认的”?
是一个有两个整数的类,使用类似的按位运算,比如模板方法,它自己的按位运算符是要走的路吗?如果是这样,有没有办法让编译器可以选择在编译时执行大部分按位运算?
编辑澄清:
我不想将 4 个不同的标志“true”、“false”、“default”、“toggle”传递给函数。
例如,想象一个圆圈被绘制在标志用于“绘制边框”、“绘制中心”、“绘制填充颜色”、“模糊边框”、“让圆圈上下跳跃”、“做任何其他幻想”的地方你可以用圆圈做的事情”,...。
对于这些“属性”中的每一个,我想传递一个值为真、假、默认或切换的标志。因此,该函数可能会决定默认绘制边框、填充颜色和居中,但其余的都不绘制。一个调用,大致是这样的:
draw_circle (DRAW_BORDER | DONT_DRAW_CENTER | TOGGLE_BLURRY_BORDER) //or
draw_circle (BORDER=true, CENTER=false, BLURRY=toggle)
//or whatever nice syntax you come up with....
Run Code Online (Sandbox Code Playgroud)
应该绘制边框(由标志指定),而不是绘制中心(由标志指定),模糊边界(标志表示:不是默认值)并绘制填充颜色(未指定,但它的默认值)。
如果我后来决定不再默认绘制中心但默认模糊边框,则调用应绘制边框(由标志指定),不绘制中心(由标志指定),不模糊边框(现在模糊是默认的) ,但我们不想要默认值)并绘制填充颜色(没有标志,但它的默认值)。
您的评论和回答让我找到了一个我喜欢并想与您分享的解决方案:
struct Default_t{} Default;
struct Toggle_t{} Toggle;
struct FlagSet{
uint m_set;
uint m_reset;
constexpr FlagSet operator|(const FlagSet other) const{
return {
~m_reset & other.m_set & ~other.m_reset |
~m_set & other.m_set & other.m_reset |
m_set & ~other.m_set,
m_reset & ~other.m_reset |
~m_set & ~other.m_set & other.m_reset|
~m_reset & other.m_set & other.m_reset};
}
constexpr FlagSet& operator|=(const FlagSet other){
*this = *this|other;
return *this;
}
};
struct Flag{
const uint m_bit;
constexpr FlagSet operator= (bool val) const{
return {(uint)val<<m_bit,(!(uint)val)<<m_bit};
}
constexpr FlagSet operator= (Default_t) const{
return {0u,0u};
}
constexpr FlagSet operator= (Toggle_t) const {
return {1u<<m_bit,1u<<m_bit};
}
constexpr uint operator& (FlagSet i) const{
return i.m_set & (1u<<m_bit);
}
constexpr operator FlagSet() const{
return {1u<<m_bit,0u}; //= set
}
constexpr FlagSet operator|(const Flag other) const{
return (FlagSet)*this|(FlagSet)other;
}
constexpr FlagSet operator|(const FlagSet other) const{
return (FlagSet)*this|other;
}
};
constexpr uint operator& (FlagSet i, Flag f){
return f & i;
}
Run Code Online (Sandbox Code Playgroud)
所以基本上它FlagSet保存两个整数。一个用于设置,一个用于重置。不同的组合代表该特定位的不同操作:
{false,false} = Default (D)
{true ,false} = Set (S)
{false,true } = Reset (R)
{true ,true } = Toggle (T)
Run Code Online (Sandbox Code Playgroud)
使用operator|相当复杂的按位运算,旨在完成
D|D = D
D|R = R|D = R
D|S = S|D = S
D|T = T|D = T
T|T = D
T|R = R|T = S
T|S = S|T = R
S|S = S
R|R = R
S|R = S (*)
R|S = R (*)
Run Code Online (Sandbox Code Playgroud)
(*) 中的非交换行为是因为我们需要能够确定哪一个是“默认”、哪一个是“用户定义”这一事实。因此,如果值发生冲突,则左侧的值优先。
该类Flag代表一个标志,基本上是一个位。使用不同的operator=()重载使得某种“键值表示法”能够直接转换为 FlagSet,并将位置处的位m_bit对设置为先前定义的对之一。默认情况下 ( operator FlagSet()) 这将转换为给定位上的 Set(S) 操作。
该类还提供了一些按位或的重载,自动转换为FlagSet并operator&()与. 在此比较中,同时考虑 Set(S) 和 Toggle(T) ,同时考虑 Reset(R) 和 Default(D) 。FlagFlagSettruefalse
使用它非常简单并且非常接近“通常的”标志实现:
constexpr Flag Flag1{0};
constexpr Flag Flag2{1};
constexpr Flag Flag3{2};
constexpr auto NoFlag1 = (Flag1=false); //Just for convenience, not really needed;
void foo(FlagSet f={0,0}){
f |= Flag1|Flag2; //This sets the default. Remember: default right, user left
cout << ((f & Flag1)?"1":"0");
cout << ((f & Flag2)?"2":"0");
cout << ((f & Flag3)?"3":"0");
cout << endl;
}
int main() {
foo();
foo(Flag3);
foo(Flag3|(Flag2=false));
foo(Flag3|NoFlag1);
foo((Flag1=Toggle)|(Flag2=Toggle)|(Flag3=Toggle));
return 0;
}
Run Code Online (Sandbox Code Playgroud)
输出:
120
123
103
023
003
Run Code Online (Sandbox Code Playgroud)
关于效率的最后一句话:虽然我没有在没有所有constexpr关键字的情况下测试它,但是使用了以下代码:
bool test1(){
return Flag1&((Flag1=Toggle)|(Flag2=Toggle)|(Flag3=Toggle));
}
bool test2(){
FlagSet f = Flag1|Flag2 ;
return f & Flag1;
}
bool test3(FlagSet f){
f |= Flag1|Flag2 ;
return f & Flag1;
}
Run Code Online (Sandbox Code Playgroud)
编译为(在gcc.godbolt.org上使用 gcc 5.3 )
test1():
movl $1, %eax
ret
test2():
movl $1, %eax
ret
test3(FlagSet):
movq %rdi, %rax
shrq $32, %rax
notl %eax
andl $1, %eax
ret
Run Code Online (Sandbox Code Playgroud)
虽然我并不完全熟悉汇编代码,但这看起来像是非常基本的位运算,并且可能是在不内联测试函数的情况下可以获得的最快速度。