我工作的代码库很老了。虽然我们使用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”来说明从用户的角度来看,它们可以是相同的。我意识到也许你可以为下一个开发人员提供可读性的案例,但除此之外,是否有理由避免使用位字段?我知道压缩字段会影响性能,但由于这些都是字符,因此填充规则仍然存在。
使用位域时需要考虑几个方面。那些(重要性取决于具体情况)
Bitfields操作在设置或读取时会产生性能损失(与直接类型相比)。 codegen的一个简单示例显示了发出的额外指令:https://gcc.godbolt.org/z/DpcErN但是,位域提供了更紧凑的数据,这对数据库更加友好,并且可能完全超过其他操作的任何缺点。理解真实性能影响的唯一方法是在实际用例中对实际应用程序进行基准测试。
位字段的Endiannes是实现定义的,因此由两个编译器生成的相同结构的布局可以不同。
没有参考绑定到位域,也不能接受它的地址。这可能会影响代码并使其不太清晰。
对于你,作为程序员,没有太大的区别。但访问整个字节的机器代码比访问单个位更简单/更短,因此使用位域可以增加生成的代码。
在伪汇编语言中,您的setter可能会变成如下:
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个成员的每个成员设置,以及类似的吸气者。它加起来。此外,即使是今天最愚蠢的编译器也可能内联全字节设置器代码,而不是实际进行子程序调用。对于位域设置器,它可能取决于您是否正在编译速度与空间的优化。
你只问过布尔人。如果它们是整数位域,则编译器必须处理加载,屏蔽先前的值,将值移位到其字段中,屏蔽掉未使用的位,将值放入and
/ or
,然后将其写回存储器。
那你为什么要用一个呢?
作为开发者,这是你的判断。如果您将同时在内存中保留许多Structure
实例,那么节省内存可能是值得的。如果你不会在内存中同时拥有该结构的许多实例,那么编译后的代码膨胀可以抵消内存节省,而且会牺牲速度。
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;