如何在C ++ 0x中组合哈希值?


87

C ++ 0x添加了hash<...>(...)

但是我无法找到一个hash_combine功能,如boost所示。实现这样的最干净的方法是什么?也许,使用C ++ 0x xor_combine

Answers:


93

好吧,就像助推器一样去做:

template <class T>
inline void hash_combine(std::size_t& seed, const T& v)
{
    std::hash<T> hasher;
    seed ^= hasher(v) + 0x9e3779b9 + (seed<<6) + (seed>>2);
}

26
是的,那也是我能做的最好的。我不明白标准委员会如何拒绝如此明显的事情。
Neil G'4

13
@Neil:我同意。我认为对于他们来说,一种简单的解决方案是库要求对std::pair(或tuple什至)具有哈希值。它将计算每个元素的哈希,然后将它们组合。(并且按照标准库的精神,以实现定义的方式。)
GManNickG 2010年

3
标准中遗漏了很多明显的东西。密集的同行评审过程使得很难把这些小东西丢掉。
stinky472 2010年

15
为什么这里有这些魔术数字?并且上述不是依赖于机器的吗(例如,在x86和x64平台上会不会有所不同)?
einpoklum 2014年

3
有一篇论文建议在此处
SSJ_GZ 2014年

35

我将在这里分享它,因为它对于寻找该解决方案的其他人可能有用:从@KarlvonMoor答案开始,这是一个可变参数的模板版本,如果您必须将多个值组合在一起,则使用起来会更麻烦:

inline void hash_combine(std::size_t& seed) { }

template <typename T, typename... Rest>
inline void hash_combine(std::size_t& seed, const T& v, Rest... rest) {
    std::hash<T> hasher;
    seed ^= hasher(v) + 0x9e3779b9 + (seed<<6) + (seed>>2);
    hash_combine(seed, rest...);
}

用法:

std::size_t h=0;
hash_combine(h, obj1, obj2, obj3);

最初编写此代码是为了实现可变参数宏,以使自定义类型易于哈希化(我认为这是hash_combine函数的主要用法之一):

#define MAKE_HASHABLE(type, ...) \
    namespace std {\
        template<> struct hash<type> {\
            std::size_t operator()(const type &t) const {\
                std::size_t ret = 0;\
                hash_combine(ret, __VA_ARGS__);\
                return ret;\
            }\
        };\
    }

用法:

struct SomeHashKey {
    std::string key1;
    std::string key2;
    bool key3;
};

MAKE_HASHABLE(SomeHashKey, t.key1, t.key2, t.key3)
// now you can use SomeHashKey as key of an std::unordered_map

为什么种子总是总是分别移位6和2?
j00hi

5

也可以通过使用可变参数模板来解决此问题,如下所示:

#include <functional>

template <typename...> struct hash;

template<typename T> 
struct hash<T> 
    : public std::hash<T>
{
    using std::hash<T>::hash;
};


template <typename T, typename... Rest>
struct hash<T, Rest...>
{
    inline std::size_t operator()(const T& v, const Rest&... rest) {
        std::size_t seed = hash<Rest...>{}(rest...);
        seed ^= hash<T>{}(v) + 0x9e3779b9 + (seed << 6) + (seed >> 2);
        return seed;
    }
};

用法:

#include <string>

int main(int,char**)
{
    hash<int, float, double, std::string> hasher;
    std::size_t h = hasher(1, 0.2f, 2.0, "Hello World!");
}

当然可以做一个模板函数,但这可能会引起一些讨厌的类型推导,例如,hash("Hallo World!")将在指针而不是字符串上计算哈希值。这可能是标准使用结构的原因。


4

几天前,我想出了这个答案的改进版本(需要C ++ 17支持):

template <typename T, typename... Rest>
void hashCombine(uint& seed, const T& v, Rest... rest)
{
    seed ^= ::qHash(v) + 0x9e3779b9 + (seed << 6) + (seed >> 2);
    (hashCombine(seed, rest), ...);
}

上面的代码在代码生成方面更好。我在代码中使用了Qt的qHash函数,但是也可以使用任何其他哈希器。


将fold表达式编写为(int[]){0, (hashCombine(seed, rest), 0)...};,它也可以在C ++ 11中使用。
亨利·门克

3

我真的很喜欢vt4a2h给出答案中的C ++ 17方法,但是它有一个问题:Rest通过值传递,而通过const引用传递它们更可取(如果必须通过const引用,则必须这样做)。适用于仅移动类型)。

这是经过修改的版本,它仍然使用fold表达式(这是它要求C ++ 17或更高版本的原因)并使用std::hash(而不是Qt哈希函数)的原因:

template <typename T, typename... Rest>
void hash_combine(std::size_t& seed, const T& v, const Rest&... rest)
{
    seed ^= std::hash<T>{}(v) + 0x9e3779b9 + (seed << 6) + (seed >> 2);
    (hash_combine(seed, rest), ...);
}

为了完整起见:此版本的版本hash_combine必须使用的所有类型都必须具有用于注入名称空间的模板规范。hashstd

例:

namespace std // Inject hash for B into std::
{
    template<> struct hash<B>
    {
        std::size_t operator()(B const& b) const noexcept
        {
            std::size_t h = 0;
            cgb::hash_combine(h, b.firstMember, b.secondMember, b.andSoOn);
            return h;
        }
    };
}

因此,B上面示例中的该类型也可以在其他类型中使用A,如以下用法示例所示:

struct A
{
    std::string mString;
    int mInt;
    B mB;
    B* mPointer;
}

namespace std // Inject hash for A into std::
{
    template<> struct hash<A>
    {
        std::size_t operator()(A const& a) const noexcept
        {
            std::size_t h = 0;
            cgb::hash_combine(h,
                a.mString,
                a.mInt,
                a.mB, // calls the template specialization from above for B
                a.mPointer // does not call the template specialization but one for pointers from the standard template library
            );
            return h;
        }
    };
}

在我看来,最好使用Hash标准容器的模板参数来指定自定义哈希,而不是将其注入std名称空间。
亨利·门克

3

vt4a2h答案肯定是不错的,但是使用C ++ 17 fold表达式,并不是每个人都可以轻松切换到较新的工具链。下面的版本使用扩展器技巧来模拟fold表达式,并且也可以在C ++ 11C ++ 14中使用。

此外,我标记了该函数inline,并对可变参数模板参数使用了完美的转发。

template <typename T, typename... Rest>
inline void hashCombine(std::size_t &seed, T const &v, Rest &&... rest) {
    std::hash<T> hasher;
    seed ^= hasher(v) + 0x9e3779b9 + (seed << 6) + (seed >> 2);
    (int[]){0, (hashCombine(seed, std::forward<Rest>(rest)), 0)...};
}

编译器资源管理器上的实时示例


看起来好多了,谢谢!我可能不在乎按值传递,因为我使用了一些隐式共享对象,例如QString。
vt4a2h 19/12/18
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.