繁体   English   中英

散列std :: unordered_map中使用的std :: array

[英]Hash an std::array which used in std::unordered_map

关于在std :: unordered_map中使用自定义散列函数,我有一个非常奇怪的问题。

我的密钥类型比int64大,所以我使用std :: array来表示它。 为了获得它的哈希值,我创建了一个MyHash类:

class MyHash
{
public:
    std::size_t operator()(const std::array<char, 12>& oid) const
    {
        Convert t;
        std::memcpy(t.arr, oid.data(), 12);
        std::cout << t.a <<" "<<t.b << std::endl;
        return (std::hash<std::int32_t>()(t.a) ^ (std::hash<std::int64_t>()(t.b) << 1)) >> 1;
    }
    union Convert {
        struct {
            std::int32_t a;
            std::int64_t b;
        };
        char arr[12];
    };
};

首先,测试一下:

std::array<char, 12> arr = {1,2,3,4,5,6,7,8,9,10,11,12};
MyHash o;
o(arr);
o(arr);

没关系。 它打印相同的tatb 现在将它与std :: unordered_map一起使用:

std::unordered_map<std::array<char, 12>, int, MyHash> map;
std::array<char, 12> arr = {1,2,3,4,5,6,7,8,9,10,11,12};
map.insert(std::make_pair(arr, 1));
auto it = map.find(arr);
if(it == map.end())
    std::cout << "error";
else
    std::cout << it->second;

现在,它会打印error ,原因是插入中的tb与find不同。 这只发生在vs release模式(或g ++ O2)

为避免未定义的行为,打包和对齐问题,您可以复制到单个整数:

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

std::size_t array_hash(const std::array<char, 12>& array) {
    std::uint64_t u64;
    std::memcpy(&u64, array.data(), 8);
    std::uint32_t u32;
    std::memcpy(&u32, array.data() + 8, 4);
    // return (std::hash<std::uint32_t>()(u32) ^ (std::hash<std::uint64_t>()(u64) << 1)) >> 1;;
    return u64 + u32; // for simplicity
}

std::size_t uint_hash(std::uint64_t u64, std::uint32_t u32) {
    // return (std::hash<std::uint32_t>()(u32) ^ (std::hash<std::uint64_t>()(u64) << 1)) >> 1;;
    return u64 + u32; // for simplicity
}

使用(g ++版本4.8.4)g ++ -S --std = c ++ 11 -O3,您将获得:

_Z10array_hashRKSt5arrayIcLm24EE:
.LFB914:
        .cfi_startproc
        movl    8(%rdi), %eax
        addq    (%rdi), %rax
        ret
        .cfi_endproc

_Z9uint_hashmj:
.LFB915:
        .cfi_startproc
        movl    %esi, %eax
        addq    %rdi, %rax
        ret
        .cfi_endproc

......这是相当理想的。

另请参阅: 键入Punning,Strict Aliasing和Optimization

我们来看看这个

  union Convert {
        struct {
            std::int32_t a;
            std::int64_t b;
        };
        char arr[12];
    };

编译器可以很好地在ab之间打包额外的字节。 因此,通过char数组进行打孔的类型不一定会覆盖struct部分。 类型punning也是C ++中的临界未定义行为; 虽然我认为你在这个特殊情况下没问题。

似乎发布版本的打包安排与调试版本不同。

许多编译器允许你指定打包安排( #pragma pack ?)但是如果我是你,我不会依赖它,因为它击败了编译器的优化策略,而且基本上也是非标准的C ++。

这有点像黑客,但你可以尝试一下,看看它是如何工作的:

struct MyHash {
    std::size_t operator()(const std::array<char, 12>& oid) const {
        auto d = reinterpret_cast<const std::uint32_t*>(oid.data());
        std::size_t prime = 31;
        std::size_t other_prime = 59;
        return d[2] + other_prime*(d[1] + prime*d[0]);
    }
};

这只能起作用,因为12是sizeof(uint32_t)的倍数。 如果尺寸发生变化,您将不得不进行调整。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM