写在前面
(本专栏仅是个人笔记本,可能有错漏部分)
哈希容器一共有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>;
- 是否缓存哈希函数结果?未知
- 不允许通过key修改value的值(set的特点决定的)
- 是否允许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的文章。
基类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种特例化分别为:
- _Traits::constant_iterators::value == true, _Traits::unique_keys::value == true,对应unordered_set
- Traits::constant_iterators::value == true, _Traits::unique_keys::value == false,对应unordered_multiset
- 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是一样的,略。