【问题标题】:When should we provide our own Hash function for `std::unordered_set`我们什么时候应该为`std::unordered_set`提供我们自己的哈希函数
【发布时间】:2013-07-16 08:04:36
【问题描述】:

当我编译以下代码时,我看到了与 Hash 相关的错误。

int F_no_meaningA(unordered_set<vector<int>>& setVec, vector<int>& vec) 
{
    setVec.insert(vec);
    return 1;
}

int main()
{
  vector<int> W{2, 3, 7}; 
  unordered_set<vector<int>> setVec; 
}

$ g++ --version
g++ (Ubuntu/Linaro 4.6.3-1ubuntu5) 4.6.3

$ g++ $1.cpp -o $1 -g -Wall -Weffc++ -pedantic -std=c++0x

/tmp/ccCQFQ4N.o:在函数中 `std::__detail::_Hash_code_base

, std::vector >, std::_Identity > >, std::equal_to > >, 标准::哈希 > >, std::__detail::_Mod_range_hashing, std::__detail::_Default_ranged_hash, 假>::_M_hash_code(std::vector > const&) 常量':/usr/include/c++/4.6/bits/hashtable_policy.h:753:未定义 参考std::hash<std::vector<int, std::allocator<int> > ::operator()(std::vector<int, std::allocator<int> >) const' /tmp/ccCQFQ4N.o: In function std::__detail::_Hash_code_base , std::vector >, std::_Identity > >, std::equal_to > >, 标准::哈希 > >, std::__detail::_Mod_range_hashing, std::__detail::_Default_ranged_hash, false>::_M_bucket_index(std::__detail::_Hash_node >, false> const*, unsigned int) const': /usr/include/c++/4.6/bits/hashtable_policy.h:763:未定义的引用 到`std::hash > ::operator()(std::vector >) const' collect2: ld 返回 1 个退出状态

那么,我引入下面自己的Hash,问题就解决了。

问题 1> 我们什么时候应该为std::unordered_set 提供我们自己的哈希? 我们什么时候应该为std::unordered_set 提供我们自己的等价函数?

struct HashVector : unary_function<vector<int>, vector<int>::size_type> {
  vector<int>::size_type operator()(const vector<int>& vec) const {
    vector<int>::size_type sum = 0;
    for(int i : vec) {
      sum = sum*37 + hash<int>()(i);
    }
    return sum;
  }
};

int F_no_meaningB(unordered_set<vector<int>, HashVector>& setVec, vector<int>& vec) 
{
    setVec.insert(vec);
    return 1;
}

int main()
{
  vector<int> W{2, 3, 7}; 
  unordered_set<vector<int>, HashVector> setVec; 
}

警告:基类‘struct std::unary_function, unsigned int>' 有一个非虚析构函数 [-Weffc++]

问题 2> 为什么 g++ 会抱怨 struct HashVector 并带有上述警告?

谢谢

【问题讨论】:

  • 因为vector&lt;int&gt; 没有内置哈希?
  • 所以你的意思是编译器不够聪明,无法使用 std::hash 迭代遍历向量中的所有元素?
  • 这不是“聪明”或“不聪明”的问题。您的解决方案是 a 解决方案,而不是散列容器的 the 解决方案。您可能还想要任何其他行为。顺便说一句,请查看 Boost.Hashhash_combinehash_range
  • @q0987 只要容器中存储的类型不是标准提供specialization 的类型,您就需要提供自己的哈希函数。如果您可以使用 Boost,它会提供 hash_range,它将遍历容器并生成哈希。

标签: c++ c++11 stl


【解决方案1】:

我们什么时候应该为std::unordered_set 提供我们自己的哈希?

当您使用的类型没有标准库提供的散列时。例如,它不提供标准容器的哈希函数,包括vector&lt;int&gt;

为什么 g++ 用上面的警告抱怨 struct HashVector?

因为您已经使用-Weffc++ 请求(有点过分热心)警告,以便在您从没有虚拟析构函数的类继承时告诉您。对于继承的大多数用途(即多态性),您不想这样做。但是,在这种情况下,继承只是用于(或者,有些人可能会说,被滥用)将一些定义注入到类中,因此警告并不表示存在问题。

std::unary_function 之类的类已被弃用,因此最好的解决方案是根本不继承它。

【讨论】:

  • 我使用std::unary_function 的原因是因为它在 Scotty Meyer 的书中提到。我不知道这种做法现在已被弃用。
  • 我宁愿说非多态继承比多态继承更常用(这使得这个编译器警告更加荒谬)。但我想这是一种风格问题(以及人们将 C++ 视为一种“面向对象” 编程语言的程度)。
  • @q0987 它在 c++11 中被贬低了。这本书可能是在c++11之前写的
【解决方案2】:

我们什么时候应该为 std::unordered_set 提供我们自己的哈希?

该标准只需要有限数量的特化,主要针对原始类型。这是因为这些原始类型具有实现可以提供的一些合理的默认“一刀切”散列函数。更复杂的类型,例如自定义类型或容器,没有明显甚至合理的默认散列,因此,您需要提供自己的散列。如果不支持您的值类型,则必须为其提供哈希函数实现。

另外,提供您自己的哈希函数的另一个原因是当您对unordered_set 中的值分布有一些额外的专家知识时。哈希表的性能对于哈希函数对于存储在表中的值的分布的适用程度非常敏感。 Here 的解释更完整。标准默认设置只是一种万能的解决方案,这意味着它简单方便,但几乎总是次优。

为什么 g++ 用上面的警告抱怨 struct HashVector?

这主要是应用与经典面向对象编程最相关的警告(使用基类作为派生类的动态多态接口)的问题。在这种情况下,不将析构函数定义为虚拟是一个非常严重的错误(这允许从基类实例正确销毁派生类(例如,delete base_ptr;)。正如迈克所建议的那样,这是因为-Weffc++ 已启用(主要应用新手级别的经典 OOP 样式警告消息)。但是,在您的代码中,继承是在通用编程的上下文中使用的,其中继承以非常不同的方式使用(主要是给一个类灌输一些基础属性和特征)。在这种情况下,基类没有虚拟析构函数不是问题,因为它不打算用于动态 多态设置,而是在静态多态设置中。

另请注意,std::unary_function(及其亲属)在最新标准 (C++11) 中已被弃用。这是因为最新标准提供了对类型自省的增强(&lt;type_traits&gt;decltype 和类型推断)。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2014-05-07
    • 2011-07-04
    • 2016-11-11
    • 2011-03-21
    • 2017-09-13
    • 2021-12-29
    • 2011-07-17
    相关资源
    最近更新 更多