我想将类的填充字节设置为0,因为我在字节级保存/加载/比较/散列实例,并且垃圾初始化填充在每个操作中引入了非确定性。
我知道这将实现我想要的(对于简单的可复制类型):
struct Example
{
Example(char a_, int b_)
{
memset(this, 0, sizeof(*this));
a = a_;
b = b_;
}
char a;
int b;
};
我不喜欢这样做,原因有两个:我喜欢构造函数初始化列表,我知道将位设置为0并不总是与零初始化相同(例如指针和浮点数不一定具有零值全部为0位。)
顺便说一下,它显然局限于可轻易复制的类型,但这对我来说不是问题,因为我上面列出的操作(在字节级加载/保存/比较/散列)无论如何都需要简单的可复制类型。 / p>
我想要的是这样的[神奇]片段:
struct Example
{
Example(char a_, int b_) : a(a_), b(b_)
{
// Leaves all members alone, and sets all padding bytes to 0.
memset_only_padding_bytes(this, 0);
}
char a;
int b;
};
我怀疑这样的事情是可能的,所以如果有人能提出一个非丑陋的选择......我全都听见了:)
答案 0 :(得分:7)
我无法在纯C ++中完全自动完成此操作。我们使用自定义代码生成系统来完成此任务(除此之外)。您可以通过一个宏来完成此操作,您可以为其提供所有成员变量名称;它只会在offsetof(memberA)+ sizeof(memberA)和offsetof(memberB)之间寻找漏洞。
或者,在成员基础上序列化/散列,而不是二进制blob。那是十种清洁剂。
哦,另外一个选项 - 你可以提供一个operator new
,它在返回之前明确清除了内存。我不是那种方法的粉丝,但它不适用于堆栈分配。
答案 1 :(得分:3)
在二进制写/读它们时,绝不应该使用填充结构。仅仅因为填充可能因平台而异,这将导致二进制不兼容。
使用某些编译器选项(如#pragma pack (push, 1)
)在定义可写结构时禁用填充,并使用#pragma pack(pop)
将其恢复。
这很遗憾地意味着你正在失去它提供的优化。 如果这是一个问题,通过仔细设计结构,您可以通过插入虚拟变量手动“填充”它们。然后零初始化变得明显,你只需为这些假人分配零。我不建议使用“手动”方法,因为它非常容易出错,但是当你使用二进制blob写时,你可能已经关注了。但无论如何,以前都是基于unpadded结构的基准。
答案 2 :(得分:2)
我遇到了类似的问题 - 只是说这是一个糟糕的设计决定(根据dasblinkenlight的评论)并不一定有帮助,因为你可能无法控制哈希代码(在我的情况下我使用的是外部库)。
一种解决方案是为您的类编写一个自定义迭代器,它迭代数据的字节并跳过填充。然后,您可以修改哈希算法以使用自定义迭代器而不是指针。一种简单的方法是将指针模板化以便它可以使用迭代器 - 因为指针和迭代器的语义是相同的,你不应该修改超出模板化的任何代码。
编辑:Boost提供了一个很好的库,可以很容易地将自定义迭代器添加到容器中:Boost.Iterator。
无论您采用哪种解决方案,最好避免对填充进行散列,因为这样做意味着您的散列算法与您的数据结构高度耦合。如果你切换数据结构(或者作为Agent_L提到,在不同的平台上使用相同的数据结构,填充方式不同),那么它将产生不同的哈希值。另一方面,如果您只对实际数据本身进行哈希处理,那么无论您以后使用何种数据结构,都将始终生成相同的哈希值。