【问题标题】:concatenating uint16_t and uint32_t values for hashing连接 uint16_t 和 uint32_t 值以进行散列
【发布时间】:2021-08-06 11:32:09
【问题描述】:

我正在尝试连接(不添加)2 个 uint16_t 结构成员和 2 个 uint32_t 结构成员,并将结果分配给 const void *p 以进行散列。我尝试实现的struct和concatenation函数如下。

struct xyz {
....
uint32_t a;
uint32_t b;
....
uint16_t c;
uint16_t d;
....
}


const void *p=concatenation(xyz.a,xyz.b,xyz.c,xyz.d) 

已编辑:

我必须使用预定义的哈希函数。最适合我的任务的哈希函数似乎是这个。

uint32_t hash(const uint32_t p[], size_t n)
{
    //Returns the hash of the 'n' 32-bit words at 'p'
}

uint32_t hash64(const uint64_t p[], size_t n)
{
   //Returns the hash of the 'n' 64-bit words at 'p'
}

【问题讨论】:

  • 如何你想连接?对齐应该是什么? concatenation 应该返回什么?谁拥有记忆?哈希函数如何知道p 的数据类型,即它应该如何工作?为什么xyz 没有std::hash 专业化?
  • 在这种情况下“连接”是什么意思。您想让 0x01 与 0x02 “连接”为 0x0102 吗?请澄清你的问题!如果是这样,(void*) 就不能保留具有这种可变大小的值!
  • 等等?这是任务?而教授希望将哈希函数的结果放入void*???

标签: c++ pointers struct concatenation


【解决方案1】:

为了散列

在这种情况下,我宁愿提供自定义哈希函数——或者专门针对 std::hash。与标准模板一起使用时,可能如下所示:

namespace std // any extension of std namespace is UB
              // sole exception: specialising templates, which we are going to do
{

template <>
struct hash<xyz>
{
    size_t operator()(xyz const& i) const
    {
        // TODO: need to calculate the value from a, b, c, and d appropriately
        return 0;
    };
};

// if xyz is polymorphic, you might need to operate on pointers
// no problem either:
template <>
struct hash<xyz*>
{
    size_t operator()(xyz const* i) const
    {
        return hash<xyz>()(*i);
        // or if hash value is type dependent:
        return i->hash(); // custom virtual hash member function needs to be implented
    }
}

// now you can have
std::unordered_set<xyz> someSet;

void demo()
{
    someSet.insert(xyz());
}

(未经测试的代码,如有错误请自行修复。)

可以在wikipedia 找到可能使用的哈希算法列表。

【讨论】:

  • 您确定要使用 unordered_set 指针吗?您当前的示例有悬空指针。
  • @AyxanHaqverdili 没有悬空指针。实例是static。使用指针取决于用例(例如xyz 是多态的),但同意,作为一个不太合适的一般示例......
  • 我没有注意到static。你是对的。
  • 嗯,让我思考。也许您可以将所有内容放在std::bitset&lt;sizeof xyz&gt; 中(甚至可以强制转换?)并对其进行哈希专业化?可能是UB ...
  • 另外:制作自己的哈希函数不是一个好主意。我认为你的建议会有很多冲突。 i.b ^ i.a 就是这样。您希望哈希函数具有良好的均匀分布。
【解决方案2】:

void* 有这样的问题:谁拥有内存?您要将指针重新解释为什么类型?

一个更类型化的解决方案是使用std::arraystd::byte 然后你至少知道你正在查看一个原始字节数组而不是别的:

#include <cstdint>
#include <array>
#include <cstddef>
#include <cstring>

auto concat(std::uint32_t a, std::uint32_t b, std::uint16_t c, std::uint16_t d) {
    std::array<std::byte, sizeof a + sizeof b + sizeof c + sizeof d> res;
    std::byte* p = res.data();
    std::memcpy(p, &a, sizeof a);
    std::memcpy(p += sizeof a, &b, sizeof b);
    std::memcpy(p += sizeof b, &c, sizeof c);
    std::memcpy(p += sizeof c, &d, sizeof d);
    return res;
}

int main() {
    std::uint32_t a = 1, b = 0;
    std::uint16_t c = 1, d = 0;

    auto res = concat(a, b, c, d);
    return 0;
}

【讨论】:

    【解决方案3】:

    有几点需要考虑:

    该哈希值是否需要跨系统移植?如果是这样,那么您将需要小心在不同系统上以相同的方式对字节进行排序。如果没有,那么实现可以更简单。

    您是否要对类的每个成员进行哈希处理,并且该类没有填充,并且成员的任何值都不应该被平等地哈希到另一个不同的值?

    如果这两种简化都适用,那么您的函数将快速且易于实现,但违反该前提条件将破坏散列。如果没有,则必须将数据序列化到缓冲区中,这实际上意味着您不能简单地返回指针。


    对于不需要可移植性的情况,这是一个超级简单的实现,并且对所有成员进行哈希处理,并且没有填充:

    xyz example;
    static_assert(std::has_unique_object_representations_v<xyz>);
    const void* p = &example;
    

    请注意,由于 NaN 的特性,这不适用于 (IEEE-754) 浮点成员。


    可以生成可跨系统移植的哈希值的更强大的解决方案是使用通用序列化方案,并对序列化结果进行哈希处理。 C++ 中没有标准的序列化功能。

    【讨论】:

      【解决方案4】:

      如果您希望值适合指针,则完整值可以是 x86 上的 32 位或 x64 上的 64 位。我假设你正在为 64 位机器编译。

      这意味着您只能容纳 2 个 uint16 和一个 uint32,或者 2 个 uint32。

      无论哪种方式,您都可以将值转换为 uint64 (c | (d &lt;&lt; 16) | (c &lt;&lt; 32)),然后将该值转换为 void*。

      编辑:为澄清起见,您不能将所有结构成员一个接一个地移位到一个指针中。您至少需要 96 位来保存打包结构,这意味着至少有两个 64 位指针。

      【讨论】:

      • 不,因为 ab 是 32 位的。所以a | (b &lt;&lt; 16) 会重叠。无论如何,接下来会发生什么?就像你说的,它不适合 64 位,那么散列函数怎么知道要散列什么?这个问题需要进一步澄清才能回答。
      • Imo 这个问题在包括所有成员的情况下无法解决,所以我已经指出了。这不是答案吗?
      猜你喜欢
      • 2012-08-20
      • 2014-07-23
      • 2013-07-19
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2015-10-17
      • 2015-01-26
      • 1970-01-01
      相关资源
      最近更新 更多