我工作的代码库很旧。虽然我们使用c ++ 11编译几乎所有内容。许多代码是多年前用c编写的。在旧的地区开发新课程时,我总是发现自己不得不选择匹配的旧方法,还是采用更现代的方法。
在大多数情况下,我宁愿尽可能使用更现代的技术。但是,我经常看到的一种常见的老方法是位域,我很难争论使用它。我们传递了很多消息,在这里很多次,它们充满了单个位的值。请看下面的例子:
class NewStructure
{
public:
const bool getValue1() const
{
return value1;
}
void setValue1(const bool input)
{
value1 = input;
}
private:
bool value1;
bool value2;
bool value3;
bool value4;
bool value5;
bool value6;
bool value7;
bool value8;
};
struct OldStructure
{
const bool getValue1() const
{
return value1;
}
void setValue1(const bool input)
{
value1 = input;
}
unsigned char value1 : 1;
unsigned char value2 : 1;
unsigned char value3 : 1;
unsigned char value4 : 1;
unsigned char value5 : 1;
unsigned char value6 : 1;
unsigned char value7 : 1;
unsigned char value8 : 1;
};
在这种情况下,新结构的大小为8个字节,旧结构的大小为1个字节。
我添加了“ getter”和“ setter”来说明从用户角度来看,它们可以相同。我意识到,也许您可以为下一个开发人员提供可读性,但是除此之外,还有理由避免位字段吗?我知道压缩字段会影响性能,但是由于这些都是字符,因此填充规则仍然存在。
答案 0 :(得分:4)
使用位域时需要考虑几件事。这些是(重要性的高低取决于情况)
在设置或读取时(与直接类型相比),位域操作会导致性能下降。一个简单的代码生成示例显示了发出的额外指令:https://gcc.godbolt.org/z/DpcErN但是,位域提供了更紧凑的数据,这变得对缓存更友好,并且可以完全弥补附加操作的任何弊端。理解实际性能影响的唯一方法是在实际用例中对实际应用进行基准测试。
位字段的字节序是实现定义的,因此两个编译器生成的相同结构的布局可以不同。
没有引用绑定到位域,也不能使用它的地址。这可能会影响代码并使其不清楚。
答案 1 :(得分:0)
对于您来说,作为程序员,没有太大的区别。但是,访问整个字节的机器代码比访问单个位要简单/短得多,因此使用位域会堆积生成的代码。
在伪汇编语言中,设置器可能会变成类似以下内容:
ldb input1,b ; get the new value into accumulator b
movb b,value1 ; put it into the variable
rts ; return from subroutine
但是对于位域来说并不是那么容易:
ldb input1,b ; get the new value into accumulator b
movb bitfields,a ; get current bitfield values into accumulator a
cmpb b,#0 ; See what to do.
brz clearvalue1: ; If it's zero, go to clearing the bit
orb #$80,a ; set the bit representing value1.
bra resume: ; skip the clearing code.
clearvalue1:
andb #$7f,a ; clear the bit representing value1
resume:
movb a,bitfields ; put the value back
rts ; return
它必须对您的8个成员的setter进行此操作,对于getter则必须执行类似的操作。它加起来。此外,即使是当今最笨拙的编译器,也可能会内联完整字节的setter代码,而不是实际进行子例程调用。对于位域设置器,可能取决于您是否要针对速度与空间进行编译优化。
您只询问了布尔值。如果它们是整数位字段,则编译器必须处理加载,屏蔽掉先前的值,将值移位以使其对齐到其字段中,屏蔽掉未使用的位,将值and
/ or
屏蔽到位,然后将其写回内存。
那你为什么要使用一个而不是另一个?
作为开发人员,这是您的判断电话。如果要一次在内存中保留Structure
的许多实例,那么节省内存可能是值得的。如果您不想一次在内存中拥有该结构的多个实例,那么编译后的代码膨胀会节省内存,而您却在牺牲速度。
答案 2 :(得分:0)
template<typename enum_type,size_t n_bits>
class bit_flags{
std::bitset<n_bits> bits;
auto operator[](enum_type bit){return bits[bit];};
auto& set(enum_type bit)){return set(bit);};
auto& reset(enum_type bit)){return set(bit);};
//go on with flip et al...
static_assert(std::is_enum<enum_type>{});
};
enum class v_flags{v1,v2,/*...*/vN};
bit_flags<v_flags,v_flags::vN+1> my_flags;
my_flags.set(v_flags::v1);
my_flags.[v_flags::v2]=true;
std::bitset
与bool
位字段一样有效。您可以将其包装在一个类中,以强制使用enum
中定义的名称中的每一位。现在,您有了一个小型但可扩展的实用程序,可用于多个bool
标志的不同集合。 C ++ 17使它更加方便:
template<auto last_flag, typename enum_type=decltype(last_flag)>
class bit_flags{
std::bitset<last_flag+1> bits;
//...
};
bit_flags<v_flags::vN+1> my_flags;