c++ map底层实现原理_GCC中unordered_(multi)set/map的实现原理 (Part 1 继承体系)

本文详细探讨了GCC中unordered_set和unordered_map的底层实现,基于_Hashtable类及其多个基类,如_Hash_code_base、_Map_base等。文章介绍了各个基类的功能,如管理哈希函数、比较操作、插入方法和重哈希策略,并讨论了哈希值缓存的条件。
摘要由CSDN通过智能技术生成

0c335480-892e-eb11-8da9-e4434bdf6706.png

写在前面

(本专栏仅是个人笔记本,可能有错漏部分)

哈希容器一共有4种,unordered_set, unordered_multiset, unordered_map, unordered_multimap。GCC试图在一个模板类_Hashtable中实现4种容器。这使得_Hashtable的代码变得较为复杂。

如何做到一个模板表达4种容器呢?libstdc++的做法是将哈希容器公有的代码抽取出来,写在_Hashtable里面,将每个哈希容器独有的代码,以模板特例化的形式,结合类似CRTP的技巧,写在基类里面。

4种哈希容器都是基于_Hashtable

unordered_set, unordered_multiset, unordered_map, unordered_multimap的底层实现皆为_Hashtable,定义于bits/hashtable.h。

以unordered_set 为例,它内含了一个指定了模板参数的_Hashtable。

//unordered_set.h
   37   /// Base types for unordered_set.
   38   template<bool _Cache>
   39     using __uset_traits = __detail::_Hashtable_traits<_Cache, true, true>;//const iterator unique_key
   40 
   41   template<typename _Value,
   42            typename _Hash = hash<_Value>,  //即 std::hash<>
   43            typename _Pred = std::equal_to<_Value>, // key equal函数(就是==)
   44            typename _Alloc = std::allocator<_Value>,
   45            typename _Tr = __uset_traits<__cache_default<_Value, _Hash>::value>>
   46     using __uset_hashtable = _Hashtable<_Value, _Value, _Alloc,
   47                                         __detail::_Identity, _Pred, _Hash, //key即自身
   48                                         __detail::_Mod_range_hashing, // %
   49                                         __detail::_Default_ranged_hash, //tag类 空的
   50                                         __detail::_Prime_rehash_policy, _Tr>; //素数rehash

再以unordered_map为例:

37   /// Base types for unordered_map.
   38   template<bool _Cache>
   39     using __umap_traits = __detail::_Hashtable_traits<_Cache, false, true>; //non-const iterator unique_key
   40 
   41   template<typename _Key,
   42            typename _Tp,
   43            typename _Hash = hash<_Key>,//即 std::hash<>
   44            typename _Pred = std::equal_to<_Key>, //key equal函数(就是==)
   45            typename _Alloc = std::allocator<std::pair<const _Key, _Tp> >,
   46            typename _Tr = __umap_traits<__cache_default<_Key, _Hash>::value>>
   47     using __umap_hashtable = _Hashtable<_Key, std::pair<const _Key, _Tp>,
   48                                         _Alloc, __detail::_Select1st, //key 是 pair第一个元素
   49                                         _Pred, _Hash, //哈希函数
   50                                         __detail::_Mod_range_hashing, // %
   51                                         __detail::_Default_ranged_hash, //  tag类 空的
   52                                         __detail::_Prime_rehash_policy, _Tr>; //素数rehash
   53

这里有个问题,什么情况下cache hash值呢?由这段代码决定:

(感谢评论区)

第一:Hash 是不是快哈希(__is_fast_hash 点进去,会发现默认都是快哈希,但是对 std::hash<long double>作了特化,认为是慢哈希)

第二个:判断 hash(value) 时是不是 nothrow 的

如果既是快哈希,又不抛异常,就不缓存哈希值了;如果是慢哈希,或者抛异常,那么这表明哈希操作可能比较耗时还有抛异常的风险,那就缓存哈希值吧。

41   template<typename _Tp, typename _Hash>
   42     using __cache_default
   43       =  __not_<__and_<// Do not cache for fast hasher.
   44                        __is_fast_hash<_Hash>,
   45                        // Mandatory to have erase not throwing.
   46                        __detail::__is_noexcept_hash<_Tp, _Hash>>>;

_Hashtable_traits

在unordered_set中,先是声明了_Hashtable_traits

// hashtable_policy.h.
template<bool _Cache_hash_code, bool _Constant_iterators, bool _Unique_keys>
struct std::__detail::_Hashtable_traits< _Cache_hash_code, _Constant_iterators, _Unique_keys >

_Hashtable_traits描述了一组哈希表的重要属性,包括:

  • _Cache_hash_code Boolean value. True if the value of the hash function is stored along with the value. This is a time-space tradeoff. Storing it may improve lookup speed by reducing the number of times we need to call the _Equal function.
  • _Constant_iterators Boolean value. True if iterator and const_iterator are both constant iterator types. This is true for unordered_set and unordered_multiset, false for unordered_map and unordered_multimap.
  • _Unique_keys Boolean value. True if the return value of _Hashtable::count(k) is always at most one, false if it may be an arbitrary number. This is true for unordered_set and unordered_map, false for unordered_multiset and unordered_multimap.

在由模板生成类代码的时候,会根据_Hashtable_traits的属性,生成不同的代码(通过模板特例化完成)。 比如unordered_set就是:

using __uset_traits = __detail::_Hashtable_traits<_Cache, true, true>;

  1. 是否缓存哈希函数结果?未知
  2. 不允许通过key修改value的值(set的特点决定的)
  3. 是否允许key重复?不允许(也是set的特点决定的)

文件分布

_Hashtabe定义于hashtable.h。

它的基类以及辅助用的一些类和函数定义于hashtable_policy.h。

继承关系

_Hashtable有6个基类,接下来我将会逐一解释。

166   template<typename _Key, typename _Value, typename _Alloc,
  167            typename _ExtractKey, typename _Equal,
  168            typename _H1, typename _H2, typename _Hash,
  169            typename _RehashPolicy, typename _Traits>
  170     class _Hashtable
  171     : public __detail::_Hashtable_base<_Key, _Value, _ExtractKey, _Equal,
  172                                        _H1, _H2, _Hash, _Traits>,
  173       public __detail::_Map_base<_Key, _Value, _Alloc, _ExtractKey, _Equal,
  174                                  _H1, _H2, _Hash, _RehashPolicy, _Traits>,
  175       public __detail::_Insert<_Key, _Value, _Alloc, _ExtractKey, _Equal,
  176                                _H1, _H2, _Hash, _RehashPolicy, _Traits>,
  177       public __detail::_Rehash_base<_Key, _Value, _Alloc, _ExtractKey, _Equal,
  178                                     _H1, _H2, _Hash, _RehashPolicy, _Traits>,
  179       public __detail::_Equality<_Key, _Value, _Alloc, _ExtractKey, _Equal,
  180                                  _H1, _H2, _Hash, _RehashPolicy, _Traits>,
  181       private __detail::_Hashtable_alloc<
  182         typename __alloctr_rebind<_Alloc,
  183           __detail::_Hash_node<_Value,
  184                                _Traits::__hash_cached::value> >::__type>

_ExtractKey,_Equal, _H1, _H2, _Hash, _RehashPolicy都要求是重载了()的类,即仿函数。

基类1:_Hashtable_base (管理_Equal)

1639   template<typename _Key, typename _Value,
 1640            typename _ExtractKey, typename _Equal,
 1641            typename _H1, typename _H2, typename _Hash, typename _Traits>
 1642   struct _Hashtable_base
 1643   : public _Hash_code_base<_Key, _Value, _ExtractKey, _H1, _H2, _Hash,
 1644                            _Traits::__hash_cached::value>,
 1645     private _Hashtable_ebo_helper<0, _Equal>

注释中说明了该类的作用为:

Helper class adding management of _Equal functor to _Hash_code_base type.

对比_Hashtable_base_Hash_code_base的模板参数,可以发现仅是多了一个typename _Equal,说明_Hashtable_base负责管理_Equal这个类,并且提供方法_M_eq用于比较两个key是否相同:

1714     const _Equal&
 1715     _M_eq() const { return _EqualEBO::_S_cget(*this); }
 1716 
 1717     _Equal&
 1718     _M_eq() { return _EqualEBO::_S_get(*this); }

基类1-1 _Hash_code_base (管理哈希函数)

_Hash_code_base 有4种特例化版本,这是如下一种,

1130   template<typename _Key, typename _Value, typename _ExtractKey,
 1131            typename _H1, typename _H2>
 1132     struct _Hash_code_base<_Key, _Value, _ExtractKey, _H1, _H2,
 1133                            _Default_ranged_hash, false>
 1134     : private _Hashtable_ebo_helper<0, _ExtractKey>,
 1135       private _Hashtable_ebo_helper<1, _H1>,
 1136       private _Hashtable_ebo_helper<2, _H2>

_Default_ranged_hash是一个空的类,目的就是对应上述这种特例化,这种特例化会用公式h(k, N) = h2(h1(k), N)处理哈希函数(类似iterator_tag,就起一个模板匹配的作用):

449  /// Default ranged hash function H. In principle it should be a
 450  /// function object composed from objects of type H1 and H2 such that
 451  /// h(k, N) = h2(h1(k), N), but that would mean making extra copies of
 452  /// h1 and h2. So instead we'll just use a tag to tell class template
 453  /// hashtable to do that composition.
 454  struct _Default_ranged_hash { };

此外还可以看到,_Hash_code_base通过继承,负责管理_ExtractKey,以及2个哈希函数H1, H2。并提供如下方法:

1167       __hash_code //调用H1,获取哈希值
 1168       _M_hash_code(const _Key& __k) const
 1169       { return _M_h1()(__k); }
 1170 
 1171       std::size_t //调用H2, 获得bucket_index 其实就是 hash % num_of_buckets
 1172       _M_bucket_index(const _Key&, __hash_code __c, std::size_t __n) const
 1173       { return _M_h2()(__c, __n); }
 1174 
 1175       std::size_t //先调用extract_key获得key,再hash,再求余得到bucket_index
 1176       _M_bucket_index(const __node_type* __p, std::size_t __n) const
 1177         noexcept( noexcept(declval<const _H1&>()(declval<const _Key&>()))
 1178                   && noexcept(declval<const _H2&>()((__hash_code)0,
 1179                                                     (std::size_t)0)) )
 1180       { return _M_h2()(_M_h1()(_M_extract()(__p->_M_v())), __n); }
 //以下两个函数体都为空,因为不cache hash值
 1182       void
 1183       _M_store_code(__node_type*, __hash_code) const
 1184       { }
 1185 
 1186       void
 1187       _M_copy_code(__node_type*, const __node_type*) const
 1188       { }
 //以下都是返回仿函数对象
 1198       const _ExtractKey& 
 1199       _M_extract() const { return __ebo_extract_key::_S_cget(*this); }
 1200 
 1201       _ExtractKey&
 1202       _M_extract() { return __ebo_extract_key::_S_get(*this); }
 1203 
 1204       const _H1&
 1205       _M_h1() const { return __ebo_h1::_S_cget(*this); }
 1206 
 1207       _H1&
 1208       _M_h1() { return __ebo_h1::_S_get(*this); }
 1209 
 1210       const _H2&
 1211       _M_h2() const { return __ebo_h2::_S_cget(*this); }
 1212 
 1213       _H2&
 1214       _M_h2() { return __ebo_h2::_S_get(*this); }

另外一种值得提的特例化就是,与上面的特例化完全相同,只不过cache = true(cache哈希值可以提高效率,原因见下一篇笔记):

1220   template<typename _Key, typename _Value, typename _ExtractKey,
 1221            typename _H1, typename _H2>
 1222     struct _Hash_code_base<_Key, _Value, _ExtractKey, _H1, _H2,
 1223                            _Default_ranged_hash, true>
 1224     : private _Hashtable_ebo_helper<0, _ExtractKey>,
 1225       private _Hashtable_ebo_helper<1, _H1>,
 1226       private _Hashtable_ebo_helper<2, _H2>
 //这里跳过了调用extract_key获得key和hash,直接求余得到bucket_index
 1264       std::size_t
 1265       _M_bucket_index(const __node_type* __p, std::size_t __n) const
 1266         noexcept( noexcept(declval<const _H2&>()((__hash_code)0,
 1267                                                  (std::size_t)0)) )
 1268       { return _M_h2()(__p->_M_hash_code, __n); }
 1269 
 1270       void
 1271       _M_store_code(__node_type* __n, __hash_code __c) const
 1272       { __n->_M_hash_code = __c; }

注释中是这么描述_Hash_code_base 的:

Encapsulates two policy issues that aren't quite orthogonal. (1) the difference between using a ranged hash function and using the combination of a hash function and a range-hashing function. In the former case we don't have such things as hash codes, so we have a dummy type as placeholder. (2) Whether or not we cache hash codes. Caching hash codes is meaningless if we have a ranged hash function.
We also put the key extraction objects here, for convenience. Each specialization derives from one or more of the template parameters to benefit from Ebo. This is important as this type is inherited in some cases by the _Local_iterator_base type used to implement local_iterator and const_local_iterator. As with any iterator type we prefer to make it as small as possible.

基类2 _Map_base (管理operator[] at()方法)

根据traits决定是否向哈希类中加入operator[] 和at()。详见我另一篇关于CRTP的文章。

蓝色海上漂:STL中的Curiously Recurring Template Pattern 奇异递归模板模式​zhuanlan.zhihu.com

基类3 _Insert (管理insert方法)

776   template<typename _Key, typename _Value, typename _Alloc,
  777            typename _ExtractKey, typename _Equal,
  778            typename _H1, typename _H2, typename _Hash,
  779            typename _RehashPolicy, typename _Traits,
  780            bool _Constant_iterators = _Traits::__constant_iterators::value,
  781            bool _Unique_keys = _Traits::__unique_keys::value>
  782     struct _Insert;

_Hashtable是4种哈希容器的底层结构,但这4种容器的功能是有差别的,具体到insert这个方法上:

  • unordered_set/map:

pair<iterator,bool> insert ( value_type&& val );

the function returns a pair object whose first element is an iterator pointing either to the newly inserted element in the container or to the element whose key is equivalent, and a bool value indicating whether the element was successfully inserted or not.
  • unordered_multiset/map:

iterator insert ( value_type&& val );

the function returns an iterator to the newly inserted element.

首先,_Insert继承了_Insert_base,_Insert_base定义了一系列4种哈希容器都需要的insert(所有哈希容器公共的部分)。

669  /**
 670  * Primary class template _Insert_base.
 671  *
 672  * insert member functions appropriate to all _Hashtables.
 673  */
 674  template<typename _Key, typename _Value, typename _Alloc,
 675  typename _ExtractKey, typename _Equal,
 676  typename _H1, typename _H2, typename _Hash,
 677  typename _RehashPolicy, typename _Traits>
 678  struct _Insert_base

其次,_Insert分别为unordered_set, unordered_multiset, unordered_(multi)map定义了3种模板特例化(每个哈希容器私有的部分),代码见这里。

3种特例化分别为:

  1. _Traits::constant_iterators::value == true, _Traits::unique_keys::value == true,对应unordered_set
  2. Traits::constant_iterators::value == true, _Traits::unique_keys::value == false,对应unordered_multiset
  3. Traits::__constant_iterators::value == false, 对应unordered_(multi)map

最后 _insert 加上 _Insert_base组成了哈希容器的一部分方法。

(其实1 和 2的代码就差一点点,真的有必要吗?)

基类4 _Rehash_base (管理rehash_policy)

_Rehash_base负责根据_RehashPolicy,在每次插入新元素前,判断是否需要rehash;当load factor过大时,获得下一个bucket size的大小。

STL只提供了一种policy,那就是_Prime_rehash_policy,它就是《源码剖析》种提到的素数rehash,它的定义在这里和这里。可以看到,max_load_factor是在_Prime_rehash_policy预设为1.0的。

_Rehash_base 的代码如下:

916  /**
 917  * Primary class template _Rehash_base.
 918  *
 919  * Give hashtable the max_load_factor functions and reserve iff the
 920  * rehash policy is _Prime_rehash_policy.
 921  */
 922  template<typename _Key, typename _Value, typename _Alloc,
 923  typename _ExtractKey, typename _Equal,
 924  typename _H1, typename _H2, typename _Hash,
 925  typename _RehashPolicy, typename _Traits>
 926  struct _Rehash_base;

  928   /// Specialization. 只提供了一种特例化,如果自定义了别的_RehashPolicy,是否要增加特例化?
  929   template<typename _Key, typename _Value, typename _Alloc,
  930            typename _ExtractKey, typename _Equal,
  931            typename _H1, typename _H2, typename _Hash, typename _Traits>
  932     struct _Rehash_base<_Key, _Value, _Alloc, _ExtractKey, _Equal,
  933                         _H1, _H2, _Hash, _Prime_rehash_policy, _Traits>
  934     {
  935       using __hashtable = _Hashtable<_Key, _Value, _Alloc, _ExtractKey,
  936                                      _Equal, _H1, _H2, _Hash,
  937                                      _Prime_rehash_policy, _Traits>;
  938 
  939       float
  940       max_load_factor() const noexcept
  941       {
  942         const __hashtable* __this = static_cast<const __hashtable*>(this);
  943         return __this->__rehash_policy().max_load_factor();
  944       }
  945 
  946       void
  947       max_load_factor(float __z)
  948       {
  949         __hashtable* __this = static_cast<__hashtable*>(this);
  950         __this->__rehash_policy(_Prime_rehash_policy(__z));
  951       }
  952 
  953       void
  954       reserve(std::size_t __n)
  955       {
  956         __hashtable* __this = static_cast<__hashtable*>(this);
  957         __this->rehash(__builtin_ceil(__n / max_load_factor()));
  958       }
  959     };
  960

基类5 _Equality 负责比较两个哈希容器是否相等

注意,不是比较两个对象是否相等,而是两个容器。

_Equality仅包含一个函数_M_equal,接受的参数是__hashtable&,即另一个哈希容器。

1780  /**
 1781  * Primary class template _Equality.
 1782  *
 1783  * This is for implementing equality comparison for unordered
 1784  * containers, per N3068, by John Lakos and Pablo Halpern.
 1785  * Algorithmically, we follow closely the reference implementations
 1786  * therein.
 1787  */
 1788   template<typename _Key, typename _Value, typename _Alloc,
 1789            typename _ExtractKey, typename _Equal,
 1790            typename _H1, typename _H2, typename _Hash,
 1791            typename _RehashPolicy, typename _Traits,
 1792            bool _Unique_keys = _Traits::__unique_keys::value>
 1793     struct _Equality;
 1794 
 1795   /// Specialization.
 1796   template<typename _Key, typename _Value, typename _Alloc,
 1797            typename _ExtractKey, typename _Equal,
 1798            typename _H1, typename _H2, typename _Hash,
 1799            typename _RehashPolicy, typename _Traits>
 1800     struct _Equality<_Key, _Value, _Alloc, _ExtractKey, _Equal,
 1801                      _H1, _H2, _Hash, _RehashPolicy, _Traits, true>
 1802     {
 1803       using __hashtable = _Hashtable<_Key, _Value, _Alloc, _ExtractKey, _Equal,
 1804                                      _H1, _H2, _Hash, _RehashPolicy, _Traits>;
 1805 
 1806       bool
 1807       _M_equal(const __hashtable&) const;
 1808     };

基类6 _Hashtable_alloc

将传入的allocator rebind形成Node的allocator,与list/forward_list是一样的,略。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值