是否有自定义类的unordered_set的默认哈希函数?

时间:2012-11-21 03:39:27

标签: c++ hash unordered-set

我第一次使用std::unordered_set并且对哈希函数有疑问。据我了解,如果您没有指定哈希函数,它将默认为std::hash<Key>

我的一个班级中有mySet成员:

typedef std::unordered_set<MyClass> USetType;
USetType mySet;

当我尝试构建时,我收到以下错误:

  

错误C2440:'type cast':无法从'const MyClass'转换为'size_t'

如果要将size_t与自定义类一起使用,是否需要定义转换函数(unordered_set)?有没有办法避免编写自己的哈希函数并只使用默认值?

2 个答案:

答案 0 :(得分:12)

如果你没有指定自己的哈希算子作为模板参数,它将默认为std::hash<MyClass>,除非你定义它,否则它不存在。

最好定义您自己在std::hash内的std的专业化:

namespace std {
  template <>
  struct hash<MyClass>
  {
    typedef MyClass      argument_type;
    typedef std::size_t  result_type;

    result_type operator()(const MyClass & t) const
    {
       /* ..calculate hash value for t */
    }
  };
}

并确保在哈希声明之前包含此代码。这样,您可以将哈希声明为std::unordered_set<MyClass>,而无需进一步的模板参数。

您没有指定MyClass内部的内容,但典型情况是您的用户定义类型只包含几个简单类型成员,其中存在默认哈希函数。在这种情况下,您可能希望将各个类型的哈希值组合为整个组合的哈希值。为此,Boost库提供了一个名为hash_combine的函数。当然,不能保证它在您的特定情况下能够很好地工作(它取决于数据值的分布和碰撞的可能性),但它提供了一个好的且易于使用的起点。

以下是如何使用它的示例,假设MyClass由两个字符串成员组成:

#include <unordered_set>
#include <boost/functional/hash.hpp>

struct MyClass
{
  std::string _s1;
  std::string _s2;
};

namespace std {
  template <>
  struct hash<MyClass>
  {
    typedef MyClass      argument_type;
    typedef std::size_t  result_type;

    result_type operator()(const MyClass & t) const
    {
      std::size_t val { 0 };
      boost::hash_combine(val,t._s1);
      boost::hash_combine(val,t._s2);
      return val;
    }
  };
}

int main()
{
  std::unordered_set<MyClass> s;
  /* ... */
  return 0;
}

答案 1 :(得分:1)

我想扩展由 jogojapan 给出的answer。正如 CashCow comment中提到的那样,您还必须为operator==使用overload the equality comparison operatorMyClass)或定义一个单独的比较功能并将其提供给unordered_set。否则,您将收到另一条错误消息。例如,VS 2013抛出:

  

错误C2678:二进制'==':未找到采用'const MyClass'类型的左操作数的运算符(或没有可接受的转换)

此外,您可以使用lambda expressions而不是定义哈希和比较函数。如果您不想使用 Boost ,那么也可以handcraft a hash function。我知道您想使用一些默认函数,但是编译器不知道如何为自定义类计算有意义的哈希。但是,您可以将std::hash用于您的班级成员。 如果将所有内容放在一起,则您的代码可以编写如下:

class MyClass {
public:
    int i;
    double d;
    std::string s;
};

int main()
{
    auto hash = [](const MyClass& mc){
        return (std::hash<int>()(mc.i) * 31 + std::hash<double>()(mc.d)) * 31 + std::hash<std::string>()(mc.s);
    };
    auto equal = [](const MyClass& mc1, const MyClass& mc2){
        return mc1.i == mc2.i && mc1.d == mc2.d && mc1.s == mc2.s;
    };
    std::unordered_set<MyClass, decltype(hash), decltype(equal)> mySet(8, hash, equal);

    return 0;
}

Code on Ideone