【问题标题】:Is there a default hash function for an unordered_set of a custom class?自定义类的 unordered_set 是否有默认哈希函数?
【发布时间】:2012-11-09 06:35:01
【问题描述】:

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

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

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

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

错误 C2440:“类型转换”:无法从“const MyClass”转换为“size_t”

如果你想在自定义类中使用unordered_set,是否需要定义一个转换函数(到size_t)?有什么办法可以避免编写自己的哈希函数而只使用默认值?

【问题讨论】:

标签: c++ hash unordered-set


【解决方案1】:

如果您不指定自己的哈希函子作为模板参数,它将默认为std::hash&lt;MyClass&gt;,除非您定义它,否则它不存在。

最好在命名空间std 内定义您自己的std::hash 特化:

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&lt;MyClass&gt;,而无需进一步的模板参数。

您没有指定 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;
}

【讨论】:

  • 与其为 MyClass 创建 std::hash 的特化,不如在其成员上添加一个使用 hash_combine 的 size_t 转换成员函数似乎更容易。我的类由 7-8 个原始类型组成,我在所有这些类型上调用 hash_combine,然后返回种子。
  • @user974967 好吧。如果您将集合声明为std::unordered_set&lt;MyClass,std::hash&lt;std::size_t&gt;&gt;,它将起作用。我想这是一个可能的解决方案,但它需要定义 operator std::size_t(),这可能意味着您在代码的其他部分会得到不希望的到整数的隐式转换。我不建议这样做。
  • 当然,定义你自己的仿函数类当然没问题。 (虽然我不同意创建 std::hash 的专业化有什么不干净的地方。)但我认为您是对的,专业化 std::hash 的唯一好处是您可以使用默认模板参数。
  • 专攻std::hash&lt;&gt;有什么“麻烦”?自定义哈希函子看起来 90% 相同......
  • 你还需要重载equal_to(或operator==)
【解决方案2】:

我想扩展 jogojapan 给出的answer。正如CashCowcomment 中提到的那样,您还必须为MyClass 提供overload the equality comparison operator (operator==) 或定义一个单独的比较函数并将其提供给@987654329 @。否则,您将收到另一条错误消息。例如,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

【讨论】:

    猜你喜欢
    • 2020-08-27
    • 2015-09-18
    • 2013-12-11
    • 2020-06-23
    • 2018-06-07
    • 2019-06-13
    • 2020-03-26
    • 1970-01-01
    • 2015-03-23
    相关资源
    最近更新 更多