【问题标题】:How do I combine hash values in C++0x?如何在 C++0x 中组合哈希值?
【发布时间】:2011-02-05 03:34:21
【问题描述】:

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

我找不到hash_combine 函数,如boost 中所示。实现这样的事情的最干净的方法是什么?也许,使用 C++0x xor_combine

【问题讨论】:

    标签: c++ c++11 boost hash std


    【解决方案1】:

    好吧,就像提拔的人那样做:

    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);
    }
    

    【讨论】:

    • 是的,这也是我能做的最好的。我不明白标准委员会如何拒绝如此明显的事情。
    • @Neil:我同意。我认为对他们来说一个简单的解决方案是要求库具有std::pair(甚至tuple)的哈希值。它会计算每个元素的哈希值,然后将它们组合起来。 (并且本着标准库的精神,以实现定义的方式。)
    • 标准中省略了很多明显的东西。密集的同行评审过程使得这些小事情很难被排除在外。
    • 为什么这里有这些神奇的数字?并且上述不是机器相关的(例如,在 x86 和 x64 平台上会不会有所不同)?
    • 我想一个好的组合方法需要了解各个部分是如何散列的......某些散列方法可能会在某些组合器上出现问题。这只是我有根据的猜测......如果它是真的,很难看出你如何能以一种明智的方式将其标准化。
    【解决方案2】:

    我将在这里分享它,因为它对寻找此解决方案的其他人有用:从 @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
    

    【讨论】:

    【解决方案3】:

    几天前我想出了this answer的稍微改进的版本(需要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 函数,但也可以使用任何其他哈希器。

    【讨论】:

    • 将折叠表达式写成(int[]){0, (hashCombine(seed, rest), 0)...};,它也适用于C++11。
    【解决方案4】:

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

    #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!") 将在指针上而不是在字符串上计算哈希值。这可能是标准使用结构的原因。

    【讨论】:

      【解决方案5】:

      answer by vt4a2h 确实不错,但使用 C++17 折叠表达式,并不是每个人都能轻松切换到更新的工具链。下面的版本使用扩展技巧来模拟折叠表达式,并且在 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)...};
      }
      

      Live example on Compiler Explorer

      【讨论】:

      • 看起来好多了,谢谢!我可能并不关心按值传递,因为我使用了一些隐式共享对象,例如 QString。
      【解决方案6】:

      我真的很喜欢 answer by vt4a2h 中的 C++17 方法,但是它存在一个问题:Rest 是按值传递的,而通过 const 引用传递它们会更可取(即如果它可以与仅移动类型一起使用,则必须这样做)。

      这是改编后的版本,它仍然使用fold expression(这就是它需要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 的类型都必须将template specialization 用于hash 注入std 命名空间。

      例子:

      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 命名空间。
      【解决方案7】:

      您可以使用我开发的rst C++ 库来做到这一点:

      #include "rst/stl/hash.h"
      
      struct Point {
        Point(const int x, const int y) : x(x), y(y) {}
      
        int x = 0;
        int y = 0;
      };
      
      bool operator==(const Point lhs, const Point rhs) {
        return (lhs.x == rhs.x) && (lhs.y == rhs.y);
      }
      
      namespace std {
      
      template <>
      struct hash<Point> {
        size_t operator()(const Point point) const {
          return rst::HashCombine({point.x, point.y});
        }
      };
      
      }
      

      【讨论】:

        【解决方案8】:

        answer by Henri Menke 效果很好,但如果您将警告视为错误,例如:

        add_compile_options(-Werror)
        

        GCC 9.3.0 会给出这个错误:

        Test.h:223:67: error: ISO C++ forbids compound-literals [-Werror=pedantic]
          223 |     (int[]){0, (hashCombine(seed, std::forward<Rest>(rest)), 0)...};
              |                                                                  ^
        cc1plus: all warnings being treated as errors
        

        我们可以更新代码来避免这样的错误:

        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 i[] = { 0, (hashCombine(seed, std::forward<Rest>(rest)), 0)... };
            (void)(i);
        }
        

        【讨论】:

          猜你喜欢
          • 2010-09-25
          • 1970-01-01
          • 1970-01-01
          • 2016-06-27
          • 1970-01-01
          • 2020-02-17
          • 2017-02-15
          • 1970-01-01
          • 2016-03-30
          相关资源
          最近更新 更多