本篇文章着重讨论如何在STL的 unordered_map 中以 struct 作为 key. 
unordered_map 是STL中的关联容器,自然就是一个模板类。其声明如下:

template < class Key,                                    // unordered_map::key_type
           class T,                                      // unordered_map::mapped_type
           class Hash = hash<Key>,                       // unordered_map::hasher
           class Pred = equal_to<Key>,                   // unordered_map::key_equal
           class Alloc = allocator< pair<const Key,T> > > class unordered_map; 
                                                         // unordered_map::allocator_type

第1个参数是key的类型,第2个参数是value的类型,第5个是内存分配模型。关键是第3个参数和第4个参数。
第3个参数 Hash 是一个一元的函数对象类型;该函数对象只有一个参数,其类型是哈希表的 Key 的类型。默认情况下,第3个参数传入的是 std::hash<key> ,它返回的是一个类型为 size_t 的数字,用途是在哈希表中定位表项所在。注意,若要自己实现第3个参数的话,首先它是一个类型(如struct),其次要实现这个类型的 operator() 方法。

那么,当哈希表发生冲突(即传入2个不同的key,但 std::hash<key> 返回的是相同的数字)的时候怎么办呢?unordered_map作为STL中哈希表的一种实现,它当然有办法来解决冲突(如开式寻址法或拉链法,这个要看STL中的具体实现);而我们得让 unordered_map 能够知道为什么这2个key不一样。这就是第4个参数的作用了。

第4个参数默认传入的是 std::equal_to<key> 函数对象,它带2个参数,也就是2个key实例,然后返回一个bool变量表示这2个key是否相等。使用者可以自己特化这个 equal_to 函数对象,但因为默认的 std::equal_to<key> 会去调用 key 类型的 operator == 函数,所以使用者其实只要实现 key 类型的 operator == 函数就可以了。

基于以上对于 unordered_map 的模板参数的分析,想把 struct或class 作为 unordered_map 的 key,就需要做以下2件事情。

1. 创建特化的 std::hash<Key> 函数作为第3个参数,以便于根据Key类型的实例来获得在哈希表中的位置。

    这一步还有另一个方法,就是自己写一个函数对象,定义 operator () 方法,这样就不用特化的 std::hash<Key> 了

2. 定义 operator == 以便于在 Hash 冲突时比较真正的key值是否相等。(operator == 被第4个参数默认的equal_to使用)

代码实例有2个。第1个请见上篇博客中的代码。第2个代码实例如下:

#include <iostream>
#include <string>
#include <unordered_map>
using namespace std;
 
template<typename T1, typename T2>
struct Node
{
    T1 x;
    T2 y;
 
    Node(T1 a, T2 b) : x(a), y(b) {}
 
    bool operator==(const Node& p) const {
        return x == p.x && y == p.y;
    }
};

// Way-1: specialized hash function for unordered_map keys
struct hash_fn
{
    template <class T1, class T2>
    std::size_t operator() (const Node<T1, T2> & node) const {
        std::size_t h1 = std::hash<T1>()(node.x);
        std::size_t h2 = std::hash<T2>()(node.y);
        return h1 ^ h2;
    }
};

// Way-2: 特化 std::hash<Node>
namespace std
{
    template<typename T1, typename T2>
    struct hash<Node<T1, T2>>
    {
        size_t operator() (const Node<T1, T2> & node) const noexcept
        {
            std::size_t h1 = std::hash<T1>()(node.x);
            std::size_t h2 = std::hash<T2>()(node.y);
            return h1 ^ h2;
        }
    };
}
 
int main()
{
    // Way-1 
    std::unordered_map< Node<std::string,std::string>, int, hash_fn  > u_map1 =
    {
        {{"C", "C99"}, 1999},
        {{"C", "C11"}, 2011},
        {{"C++", "C++14"}, 2014},
        {{"C++", "C++17"}, 2017},
        {{"Java", "Java SE 8"}, 2014},
        {{"Java", "Java SE 9"}, 2017}
    };
 
    cout << "Way-1: \n";
    for (const auto &entry: u_map1)
    {
        std::cout << "{" << entry.first.x << "," << entry.first.y << "}: "
                  << entry.second << '\n';
    }
    
    // Way-2 
    std::unordered_map< Node<std::string,std::string>, int > u_map2 =
    {
        {{"C", "C99"}, 1999},
        {{"C", "C11"}, 2011},
        {{"C++", "C++14"}, 2014},
        {{"C++", "C++17"}, 2017},
        {{"Java", "Java SE 8"}, 2014},
        {{"Java", "Java SE 9"}, 2017}
    };
    
    cout << "Way-2: \n";
    for (const auto &entry: u_map2)
    {
        std::cout << "{" << entry.first.x << "," << entry.first.y << "}: "
                  << entry.second << '\n';
    }
 
    return 0;
}

(完)
 

Logo

权威|前沿|技术|干货|国内首个API全生命周期开发者社区

更多推荐