目录
[SGI-STL 哈希容器源码与框架分析](#SGI-STL 哈希容器源码与框架分析)
[stl_hash_set.h 源码核心](#stl_hash_set.h 源码核心)
[stl_hash_map.h 源码核心](#stl_hash_map.h 源码核心)
[stl_hashtable.h 哈希表底层源码](#stl_hashtable.h 哈希表底层源码)
[常见疑问:既然 Value 控制节点存储类型,为什么还需要第二个模板参数 Key?](#常见疑问:既然 Value 控制节点存储类型,为什么还需要第二个模板参数 Key?)
模拟实现unordered_map和unordered_set
[1.迭代封装通用哈希表底层(适配 unordered_set /unordered_map)](#1.迭代封装通用哈希表底层(适配 unordered_set /unordered_map))
[一:初始版本:耦合 pair 的哈希表(仅支持 unordered_map)](#一:初始版本:耦合 pair 的哈希表(仅支持 unordered_map))
[二:搭建初始 unordered_set、unordered_map 外层框架](#二:搭建初始 unordered_set、unordered_map 外层框架)
[四:新增 Key 萃取仿函数,解耦 Key 获取逻辑](#四:新增 Key 萃取仿函数,解耦 Key 获取逻辑)
[五:扩展哈希表模板参数,接收 KeyOfT 萃取仿函数](#五:扩展哈希表模板参数,接收 KeyOfT 萃取仿函数)
[Hash Table.h](#Hash Table.h)
引言:
提示:阅读本文前,需要掌握哈希表和底层封装的思想------------C++ 高阶数据结构:哈希表万字详解 | 哈希冲突、负载因子、扩容机制、链地址法与开放定址法实现【STL unordered系列底层】-CSDN博客
C++ STL map 与 set 底层剖析与模拟实现万字详解|基于红黑树,复刻 SGI-STL 泛型复用架构-CSDN博客
在 C++ STL 容器体系中,map/set依托红黑树实现有序查找,时间复杂度为 \(O(logN)\);而 C++11 正式引入的unordered_map/unordered_set基于哈希表实现,能够在平均 O(1) 时间复杂度下完成增删查操作,是高频查询场景的首选容器。
不少开发者仅熟练使用无序容器,但对其底层原理、桶结构、哈希映射、冲突解决、扩容机制、迭代器遍历逻辑一知半解,尤其不理解: 为什么 unordered_map 存储 pair、unordered_set 存储普通键,却能复用同一套哈希表底层? 为什么 Key 不能修改、Value 可以修改? STL 如何通过模板 + 仿函数萃取实现完全解耦?
事实上 C++11 的无序容器设计完全沿袭 SGI-STL 早期 hash_map/hash_set 的底层架构。STL 采用底层通用哈希表 + 上层容器适配封装的分层思想,通过「键萃取仿函数、哈希仿函数、相等判断仿函数」三套策略类,彻底解耦数据存储、哈希映射、元素比对逻辑,实现一套底层结构复用两种上层容器。
本文将 从 SGI-STL 源码溯源 → 逐层迭代手写重构 → 完整实现迭代器、扩容、删除、查找、operator \[\] ,从零复刻一版贴合标准库设计的 unordered_set/unordered_map,彻底吃透哈希容器的底层核心精髓。
那么话不多说,接下来进入正文------------------------------>

SGI-STL 哈希容器源码与框架分析
无序哈希容器核心接口释义
理解哈希表底层结构之后,我们再去看哈希表专属接口,一切就很好理解了

bucket_count------返回有多少个桶
max_bucket_count------最大桶数量
bucket_size------获取指定桶长度
bucket------给一个Key,获取这个Key在哪个桶
load_factor------当前负载因子
max_load_factor------最大负载因子(1)
rehash------可以类比为普通容器的 resize,但存在明显区别:扩容时会从 SGI 预设素数表选取容量,最终开辟的桶数量大于等于传入的参数 n
reserve------和 rehash 使用体感十分接近,reserve 本质上是 rehash 的上层封装。设计 reserve 主要目的是和 STL 其他容器接口风格保持统一,提升 API 一致性。
头文件依赖
我们当前分析的版本为 SGI-STL 3.0。需要注意:SGI-STL 3.0 属于 C++11 之前的 STL 实现,源码内部并不存在unordered_map与unordered_set,这两个无序容器是 C++11 标准正式引入的。
但 SGI-STL 3.0 内部已经实现了哈希表结构,对应的容器名为hash_map和hash_set,属于非标准扩展容器 。这里的 "非标准" 含义是:C++ 标准并未强制要求编译器实现这组容器。相关底层源码分布在 hash_map.h、hash_set.h、stl_hash_map.h、stl_hash_set.h、stl_hashtable.h 等文件中。
cpp
//hash_set头文件依赖(对标C++11的unordered_set)
#ifndef __SGI_STL_HASH_SET
#define __SGI_STL_HASH_SET
#ifndef __SGI_STL_INTERNAL_HASHTABLE_H
#include <stl_hashtable.h>
#endif
#include <stl_hash_set.h>
//hash_map头文件依赖(对标C++11的unordered_map)
#ifndef __SGI_STL_HASH_MAP
#define __SGI_STL_HASH_MAP
#ifndef __SGI_STL_INTERNAL_HASHTABLE_H
#include <stl_hashtable.h>
#endif
#include <stl_hash_map.h>
从头文件依赖可以看出:hash_map 和 hash_set 底层共用同一套数据结构,核心实现都放在 stl_hashtable.h ,也就是哈希表。
stl_hash_set.h 源码核心
cpp
#ifndef __STL_LIMITED_DEFAULT_TEMPLATES
template <class Value, class HashFcn = hash<Value>,
class EqualKey = equal_to<Value>,
class Alloc = alloc>
#else
template <class Value, class HashFcn, class EqualKey, class Alloc = alloc>
#endif
class hash_set
{
private:
typedef hashtable<Value, Value, HashFcn, identity<Value>,
EqualKey, Alloc> ht;
ht rep;
public:
typedef typename ht::key_type key_type;
typedef typename ht::value_type value_type;
};
hash_set 中存储的 Value 就是 Key,传入两个相同的 Value,目的是和下方stl_hash_map的模板设计保持统一。
stl_hash_map.h 源码核心
cpp
#ifndef __STL_LIMITED_DEFAULT_TEMPLATES
template <class Key, class T, class HashFcn = hash<Key>,
class EqualKey = equal_to<Key>,
class Alloc = alloc>
#else
template <class Key, class T, class HashFcn, class EqualKey,
class Alloc = alloc>
#endif
class hash_map
{
private:
typedef hashtable<pair<const Key, T>, Key, HashFcn,
select1st<pair<const Key, T> >, EqualKey, Alloc> ht;
ht rep;
public:
typedef typename ht::key_type key_type;
typedef T data_type;
typedef T mapped_type;
typedef typename ht::value_type value_type;
};
重点:unordered_map 的 value_type 并不是 T,而是 pair<const Key, T>。
将 Key 设置为 const,目的是防止用户迭代过程中修改 Key,一旦 Key 被修改,哈希映射位置失效,会破坏哈希表结构。
stl_hashtable.h 哈希表底层源码
cpp
template <class Value>
struct __hashtable_node
{
__hashtable_node* next;
Value val;
};
template <class Value, class Key, class HashFcn,
class ExtractKey, class EqualKey,
class Alloc>
class hashtable {
public:
typedef Key key_type;
typedef Value value_type;
typedef HashFcn hasher;
typedef EqualKey key_equal;
typedef size_t size_type;
typedef ptrdiff_t difference_type;
typedef value_type* pointer;
typedef const value_type* const_pointer;
typedef value_type& reference;
typedef const value_type& const_reference;
hasher hash_funct() const { return hash; }
key_equal key_eq() const { return equals; }
private:
hasher hash;
key_equal equals;
ExtractKey get_key;
typedef __hashtable_node<Value> node;
typedef simple_alloc<node, Alloc> node_allocator;
vector<node*,Alloc> buckets;
size_type num_elements;
};
这套封装思路和 map、set 依托红黑树的设计高度一致。
注意底层hashtable模板参数:Value 代表节点存储的数据,Key 代表用于哈希、比较的关键字,和我们自己手写简易哈希表的参数顺序刚好相反,但不影响功能逻辑。
框架核心思想解读
通过源码可以看到,结构上hash_map、hash_set与map、set思路高度相似:复用同一个底层容器 hashtable。
hash_set 向 hashtable 传入相同的 Key 与 Value,搭配 identity 萃取器直接取值;
hash_map 底层存储 pair<const Key, T>,依靠键萃取仿函数select1st(即模板参数列表中的ExtractKey) 从 pair 中提取 Key,以此实现 K-V 模型。
常见疑问:既然 Value 控制节点存储类型,为什么还需要第二个模板参数 Key?
很多人学到这里都会产生疑惑:unordered_set 场景下 Key 和 Value 完全相同,为什么要额外多出 Key 参数?------原理和 set/map 复用红黑树的设计思想同源
核心原因: find()、erase() 这类接口接收的参数类型是 Key。
- set:插入元素类型 = 查找元素类型(都是 Key);
- map:插入的是完整 pair<const K,T>,但查找、删除只需要传入 Key。
如果不单独抽离 Key 作为模板参数,底层哈希表无法统一处理两种场景的查找入参。
附:我们基于手写哈希表封装 hash_set(C++11 unordered_set) 与 hash_map(C++11 unordered_map) 时,可以先实现最简版本:暂时只支持基础 key/value 模型。只要理解这套分层思想,后续拓展自定义比较器、unordered_multiset/unordered_multimap 只是增量开发。
模拟实现unordered_map和unordered_set
1.迭代封装通用哈希表底层(适配 unordered_set /unordered_map)
我们先实现基础版本哈希表,节点内部直接存储pair<K,V>。该版本哈希表强绑定键值对结构,只能给 unordered_map 使用,无法适配只存储单个 key 的 unordered_set。后续借助模板 + 萃取仿函数抽取 key的思路进行改造,让同一套哈希表底层同时支撑 unordered_set 和 unordered_map。
这部分的思路和封装map和set部分的思路是极其相似的,所以这里我就不过多讲解了
一:初始版本:耦合 pair 的哈希表(仅支持 unordered_map)
Hash Table.h
cpp
#pragma once
#include <iostream>
#include <vector>
#include <string>
#include <algorithm>
using namespace std;
inline unsigned long __stl_next_prime(unsigned long n)
{
// Note: assumes long is at least 32 bits.
static const int __stl_num_primes = 28;
static const unsigned long __stl_prime_list[__stl_num_primes] =
{
53, 97, 193, 389, 769,
1543, 3079, 6151, 12289, 24593,
49157, 98317, 196613, 393241, 786433,
1572869, 3145739, 6291469, 12582917, 25165843,
50331653, 100663319, 201326611, 402653189, 805306457,
1610612741, 3221225473, 4294967291
};
const unsigned long* first = __stl_prime_list;
const unsigned long* last = __stl_prime_list + __stl_num_primes;
const unsigned long* pos = lower_bound(first, last, n);
return pos == last ? *(last - 1) : *pos;
}
template<class K, class V>
class HashNode
{
using Node = HashNode<K, V>;
public:
HashNode() = default;
HashNode(const pair<K, V>& kv)
:_kv(kv)
{
}
pair<K, V> _kv;
Node* _next = nullptr;
};
template<class K>
class Hash
{
public:
size_t operator()(const K& k)
{
return (size_t)k;
}
};
template<>
class Hash<string>
{
public:
size_t operator()(const string& s)
{
size_t ret = 0;
for (auto c : s)
{
ret = ret * 131 + c;
}
return ret;
}
};
template<class K, class V>
class HashTable
{
using Node = HashNode<K, V>;
public:
HashTable()
:_tables(__stl_next_prime(0))
, _n(0)
{
}
HashTable(const HashTable<K, V>& ht)
:_tables(__stl_next_prime(ht._tables.size()))
, _n(0)
{
for (size_t i = 0; i < ht._tables.size(); i++)
{
Node* cur = ht._tables[i];
while (cur)
{
Insert(cur->_kv);
cur = cur->_next;
}
}
}
HashTable<K, V>& operator=(HashTable<K, V> ht)
{
swap(ht);
return *this;
}
~HashTable()
{
for (size_t i = 0; i < _tables.size(); i++)
{
Node* cur = _tables[i];
while (cur)
{
Node* next = cur->_next;
delete cur;
cur = next;
}
_tables[i] = nullptr;
}
}
void swap(HashTable<K, V>& ht)
{
std::swap(this->_tables, ht._tables);
std::swap(this->_n, ht._n);
}
bool Insert(const pair<K, V>& kv)
{
if (Find(kv.first))
return false;
if (_n == _tables.size())
{
vector<Node*> newtables(__stl_next_prime(_tables.size() + 1), nullptr);
for (int i = 0; i < _tables.size(); i++)
{
if (_tables[i])
{
Node* cur = _tables[i];
while (cur)
{
Node* next = cur->_next;
size_t hash0 = Hash<K>()(cur->_kv.first) % newtables.size();
cur->_next = newtables[hash0];
newtables[hash0] = cur;
cur = next;
}
_tables[i] = nullptr;
}
}
_tables.swap(newtables);
}
size_t hash0 = Hash<K>()(kv.first) % _tables.size();
Node* newnode = new Node(kv);
newnode->_next = _tables[hash0];
_tables[hash0] = newnode;
++_n;
return true;
}
bool Erase(const K& k)
{
size_t hash0 = Hash<K>()(k) % _tables.size();
Node* cur = _tables[hash0];
Node* parent = nullptr;
while (cur)
{
if (cur->_kv.first == k)
{
if (parent)
{
parent->_next = cur->_next;
}
else
{
_tables[hash0] = cur->_next;
}
delete cur;
--_n;
return true;
}
else
{
parent = cur;
cur = cur->_next;
}
}
return false;
}
Node* Find(const K& k)
{
size_t hash0 = Hash<K>()(k) % _tables.size();
Node* cur = _tables[hash0];
while (cur)
{
if (cur->_kv.first == k)
return cur;
else
cur = cur->_next;
}
return nullptr;
}
private:
vector<Node*> _tables;
size_t _n = 0;//数据个数
};
二:搭建初始 unordered_set、unordered_map 外层框架
unordered_set.h
cpp
template<class K>
class unordered_set
{
using ht = HashTable<K, K>;
public:
bool insert(const K& kv)
{
return _ht.Insert(kv);
}
bool erase(const K& k)
{
return _ht.Erase(k);
}
private:
ht _ht;
};
unordered_map.h
cpp
template<class K,class V>
class unordered_map
{
using ht = HashTable<K, pair<const K, V>>;
public:
bool insert(const pair<const K, V>& kv)
{
return _ht.Insert(kv);
}
bool erase(const K& k)
{
return _ht.Erase(k);
}
private:
ht _ht;
};
三:修改哈希表节点与类模板,泛化存储类型
我们将节点存储内容泛化为模板参数T:
cpp
template<class K, class T>
class HashNode
{
using Node = HashNode<K, T>;
public:
HashNode() = default;
HashNode(const T& kv)
:_data(kv)
{
}
T _data;
Node* _next = nullptr;
};
template<class K, class T>
class HashTable
{
using Node = HashNode<K, T>;
bool Insert(const T& kv);
}
此时出现核心矛盾:底层哈希表无法预知模板参数T是什么类型。 T可能是unordered_set存储的 Key,也可能是unordered_map存储的pair<const K,V>。
如果直接使用类型原生比较运算符:pair 默认operator==会同时对比 key 与 value;但哈希容器规则要求仅依靠 key 判断元素重复,不能对比 value。
解决方案:在unordered_set、unordered_map内部各自定义 Key 萃取仿函数,负责从存储对象T中提取关键字 Key;将萃取仿函数作为模板参数传入底层哈希表。哈希表内部统一调用仿函数获取 key,彻底消除硬编码.first。
四:新增 Key 萃取仿函数,解耦 Key 获取逻辑
unordered_set.h
cpp
#pragma once
#include "Hash Table.h"
namespace qiu
{
template<class K>
class unordered_set
{
struct SetKeyOfT;
using ht = HashTable<K, K, SetKeyOfT>;
public:
struct SetKeyOfT
{
const K& operator()(const K& key)
{
return key;
}
};
bool insert(const K& kv)
{
return _ht.Insert(kv);
}
bool erase(const K& k)
{
return _ht.Erase(k);
}
private:
ht _ht;
};
}
unordered_map.h
cpp
#pragma once
#include "Hash Table.h"
namespace qiu
{
template<class K,class V>
class unordered_map
{
struct MapKeyOfT;
using ht = HashTable<K, pair<const K, V>, MapKeyOfT>;
public:
struct MapKeyOfT
{
const K& operator()(const pair<const K,V>& kv)
{
return kv.first;
}
};
bool insert(const pair<const K, V>& kv)
{
return _ht.Insert(kv);
}
bool erase(const K& k)
{
return _ht.Erase(k);
}
private:
ht _ht;
};
}
五:扩展哈希表模板参数,接收 KeyOfT 萃取仿函数
给 Hashtable 增加模板参数KeyOfT接收上层传入的 key 提取仿函数,把代码里所有硬编码_data.first全部替换为仿函数调用。重点改造 insert , fiind 和 erase 接口,其余接口同步适配模板参数。
cpp
#pragma once
#include <iostream>
#include <vector>
#include <string>
#include <algorithm>
using namespace std;
inline unsigned long __stl_next_prime(unsigned long n)
{
// Note: assumes long is at least 32 bits.
static const int __stl_num_primes = 28;
static const unsigned long __stl_prime_list[__stl_num_primes] =
{
53, 97, 193, 389, 769,
1543, 3079, 6151, 12289, 24593,
49157, 98317, 196613, 393241, 786433,
1572869, 3145739, 6291469, 12582917, 25165843,
50331653, 100663319, 201326611, 402653189, 805306457,
1610612741, 3221225473, 4294967291
};
const unsigned long* first = __stl_prime_list;
const unsigned long* last = __stl_prime_list + __stl_num_primes;
const unsigned long* pos = lower_bound(first, last, n);
return pos == last ? *(last - 1) : *pos;
}
template<class T>
class HashNode
{
using Node = HashNode<T>;
public:
HashNode() = default;
HashNode(const T& kv)
:_data(kv)
{
}
T _data;
Node* _next = nullptr;
};
template<class K>
class Hash
{
public:
size_t operator()(const K& k)
{
return (size_t)k;
}
};
template<>
class Hash<string>
{
public:
size_t operator()(const string& s)
{
size_t ret = 0;
for (auto c : s)
{
ret = ret * 131 + c;
}
return ret;
}
};
template<class K, class T,class KeyOfT>
class HashTable
{
using Node = HashNode<T>;
public:
HashTable()
:_tables(__stl_next_prime(0))
, _n(0)
{
}
HashTable(const HashTable<K, T, KeyOfT>& ht)
:_tables(__stl_next_prime(ht._tables.size()))
, _n(0)
{
for (size_t i = 0; i < ht._tables.size(); i++)
{
Node* cur = ht._tables[i];
while (cur)
{
Insert(cur->_data);
cur = cur->_next;
}
}
}
HashTable<K, T, KeyOfT>& operator=(HashTable<K, T, KeyOfT> ht)
{
swap(ht);
return *this;
}
~HashTable()
{
for (size_t i = 0; i < _tables.size(); i++)
{
Node* cur = _tables[i];
while (cur)
{
Node* next = cur->_next;
delete cur;
cur = next;
}
_tables[i] = nullptr;
}
}
void swap(HashTable<K, T, KeyOfT>& ht)
{
std::swap(this->_tables, ht._tables);
std::swap(this->_n, ht._n);
}
bool Insert(const T& kv)
{
KeyOfT kot;
if (Find(kot(kv)))
return false;
if (_n == _tables.size())
{
vector<Node*> newtables(__stl_next_prime(_tables.size() + 1), nullptr);
for (int i = 0; i < _tables.size(); i++)
{
if (_tables[i])
{
Node* cur = _tables[i];
while (cur)
{
Node* next = cur->_next;
size_t hash0 = Hash<K>()(kot(cur->_data)) % newtables.size();
cur->_next = newtables[hash0];
newtables[hash0] = cur;
cur = next;
}
_tables[i] = nullptr;
}
}
_tables.swap(newtables);
}
size_t hash0 = Hash<K>()(kot(kv)) % _tables.size();
Node* newnode = new Node(kv);
newnode->_next = _tables[hash0];
_tables[hash0] = newnode;
++_n;
return true;
}
bool Erase(const K& k)
{
size_t hash0 = Hash<K>()(k) % _tables.size();
Node* cur = _tables[hash0];
Node* parent = nullptr;
while (cur)
{
KeyOfT kot;
if (kot(cur->_data) == k)
{
if (parent)
{
parent->_next = cur->_next;
}
else
{
_tables[hash0] = cur->_next;
}
delete cur;
--_n;
return true;
}
else
{
parent = cur;
cur = cur->_next;
}
}
return false;
}
Node* Find(const K& k)
{
KeyOfT kot;
size_t hash0 = Hash<K>()(k) % _tables.size();
Node* cur = _tables[hash0];
while (cur)
{
if (kot(cur->_data) == k)
return cur;
else
cur = cur->_next;
}
return nullptr;
}
private:
vector<Node*> _tables;
size_t _n = 0;//数据个数
};
当前版本哈希函数Hash<K>仍然硬编码在哈希表内部,下一步我们将把哈希仿函数提升为模板参数,对外支持自定义哈希规则
六:哈希映射仿函数抽离为模板参数,支持自定义哈希
改造思路比较清晰:将哈希表内部使用的哈希映射仿函数,一路向上透传,作为unordered_set、unordered_map模板参数,并设置默认实参。上层容器接收哈希仿函数后,转发给底层HashTable。
unordered_set.h
cpp
#pragma once
#include "Hash Table.h"
namespace qiu
{
template<class K,class Hash = HashKey<K>>
class unordered_set
{
struct SetKeyOfT;
using ht = HashTable<K, K, SetKeyOfT, Hash>;
public:
struct SetKeyOfT
{
const K& operator()(const K& key)
{
return key;
}
};
bool insert(const K& kv)
{
return _ht.Insert(kv);
}
bool erase(const K& k)
{
return _ht.Erase(k);
}
private:
ht _ht;
};
}
unordered_map.h
cpp
#pragma once
#include "Hash Table.h"
namespace qiu
{
template<class K,class V, class Hash = HashKey<K>>
class unordered_map
{
struct MapKeyOfT;
using ht = HashTable<K, pair<const K, V>, MapKeyOfT, Hash>;
public:
struct MapKeyOfT
{
const K& operator()(const pair<const K,V>& kv)
{
return kv.first;
}
};
bool insert(const pair<const K, V>& kv)
{
return _ht.Insert(kv);
}
bool erase(const K& k)
{
return _ht.Erase(k);
}
private:
ht _ht;
};
}
Hash Table.h
cpp
#pragma once
#include <iostream>
#include <vector>
#include <string>
#include <algorithm>
using namespace std;
inline unsigned long __stl_next_prime(unsigned long n)
{
// Note: assumes long is at least 32 bits.
static const int __stl_num_primes = 28;
static const unsigned long __stl_prime_list[__stl_num_primes] =
{
53, 97, 193, 389, 769,
1543, 3079, 6151, 12289, 24593,
49157, 98317, 196613, 393241, 786433,
1572869, 3145739, 6291469, 12582917, 25165843,
50331653, 100663319, 201326611, 402653189, 805306457,
1610612741, 3221225473, 4294967291
};
const unsigned long* first = __stl_prime_list;
const unsigned long* last = __stl_prime_list + __stl_num_primes;
const unsigned long* pos = lower_bound(first, last, n);
return pos == last ? *(last - 1) : *pos;
}
template<class T>
class HashNode
{
using Node = HashNode<T>;
public:
HashNode() = default;
HashNode(const T& kv)
:_data(kv)
{
}
T _data;
Node* _next = nullptr;
};
template<class K>
class HashKey
{
public:
size_t operator()(const K& k)
{
return (size_t)k;
}
};
template<>
class HashKey<string>
{
public:
size_t operator()(const string& s)
{
size_t ret = 0;
for (auto c : s)
{
ret = ret * 131 + c;
}
return ret;
}
};
template<class K, class T,class KeyOfT, class Hash>
class HashTable
{
using Node = HashNode<T>;
public:
HashTable()
:_tables(__stl_next_prime(0))
, _n(0)
{
}
HashTable(const HashTable<K, T, KeyOfT, Hash>& ht)
:_tables(__stl_next_prime(ht._tables.size()))
, _n(0)
{
for (size_t i = 0; i < ht._tables.size(); i++)
{
Node* cur = ht._tables[i];
while (cur)
{
Insert(cur->_data);
cur = cur->_next;
}
}
}
HashTable<K, T, KeyOfT, Hash>& operator=(HashTable<K, T, KeyOfT, Hash> ht)
{
swap(ht);
return *this;
}
~HashTable()
{
for (size_t i = 0; i < _tables.size(); i++)
{
Node* cur = _tables[i];
while (cur)
{
Node* next = cur->_next;
delete cur;
cur = next;
}
_tables[i] = nullptr;
}
}
void swap(HashTable<K, T, KeyOfT, Hash>& ht)
{
std::swap(this->_tables, ht._tables);
std::swap(this->_n, ht._n);
}
bool Insert(const T& kv)
{
KeyOfT kot;
Hash hk;
if (Find(kot(kv)))
return false;
if (_n == _tables.size())
{
vector<Node*> newtables(__stl_next_prime(_tables.size() + 1), nullptr);
for (int i = 0; i < _tables.size(); i++)
{
if (_tables[i])
{
Node* cur = _tables[i];
while (cur)
{
Node* next = cur->_next;
size_t hash0 = hk(kot(cur->_data)) % newtables.size();
cur->_next = newtables[hash0];
newtables[hash0] = cur;
cur = next;
}
_tables[i] = nullptr;
}
}
_tables.swap(newtables);
}
size_t hash0 = hk(kot(kv)) % _tables.size();
Node* newnode = new Node(kv);
newnode->_next = _tables[hash0];
_tables[hash0] = newnode;
++_n;
return true;
}
bool Erase(const K& k)
{
Hash hk;
size_t hash0 = hk(k) % _tables.size();
Node* cur = _tables[hash0];
Node* parent = nullptr;
while (cur)
{
KeyOfT kot;
if (kot(cur->_data) == k)
{
if (parent)
{
parent->_next = cur->_next;
}
else
{
_tables[hash0] = cur->_next;
}
delete cur;
--_n;
return true;
}
else
{
parent = cur;
cur = cur->_next;
}
}
return false;
}
Node* Find(const K& k)
{
Hash hk;
KeyOfT kot;
size_t hash0 = hk(k) % _tables.size();
Node* cur = _tables[hash0];
while (cur)
{
if (kot(cur->_data) == k)
return cur;
else
cur = cur->_next;
}
return nullptr;
}
private:
vector<Node*> _tables;
size_t _n = 0;//数据个数
};
本次改造完成后,哈希算法不再被底层哈希表写死。外部使用者可以自定义仿函数传入unordered_set/unordered_map,实现自定义哈希策略。
当前遗留局限:
元素相等判断依旧硬编码使用==运算符,没有抽离相等谓词。接下来我继续新增比较仿函数模板参数,进一步对齐 C++ 标准unordered容器设计。
七:相等判断逻辑抽离为谓词仿函数,提升为模板参数
改造思路和第六步哈希仿函数抽离基本一致,就不过多赘述了
unordered_set.h
cpp
#pragma once
#include "Hash Table.h"
namespace qiu
{
template<class K,class Hash = HashKey<K>,class Pred = Equal<K>>
class unordered_set
{
struct SetKeyOfT;
using ht = HashTable<K, K, SetKeyOfT, Hash, Pred>;
public:
struct SetKeyOfT
{
const K& operator()(const K& key)
{
return key;
}
};
bool insert(const K& kv)
{
return _ht.Insert(kv);
}
bool erase(const K& k)
{
return _ht.Erase(k);
}
HashNode<K>* find(const K& key)
{
return _ht.Find(key);
}
private:
ht _ht;
};
}
unordered_map.h
cpp
#pragma once
#include "Hash Table.h"
namespace qiu
{
template<class K,class V, class Hash = HashKey<K>, class Pred = Equal<K>>
class unordered_map
{
struct MapKeyOfT;
using ht = HashTable<K, pair<const K, V>, MapKeyOfT, Hash, Pred>;
public:
struct MapKeyOfT
{
const K& operator()(const pair<const K,V>& kv)
{
return kv.first;
}
};
bool insert(const pair<const K, V>& kv)
{
return _ht.Insert(kv);
}
bool erase(const K& k)
{
return _ht.Erase(k);
}
HashNode<pair<const K, V>>* find(const K& key)
{
return _ht.Find(key);
}
private:
ht _ht;
};
}
Hash Table.h
cpp
#pragma once
#include <iostream>
#include <vector>
#include <string>
#include <algorithm>
using namespace std;
inline unsigned long __stl_next_prime(unsigned long n)
{
// Note: assumes long is at least 32 bits.
static const int __stl_num_primes = 28;
static const unsigned long __stl_prime_list[__stl_num_primes] =
{
53, 97, 193, 389, 769,
1543, 3079, 6151, 12289, 24593,
49157, 98317, 196613, 393241, 786433,
1572869, 3145739, 6291469, 12582917, 25165843,
50331653, 100663319, 201326611, 402653189, 805306457,
1610612741, 3221225473, 4294967291
};
const unsigned long* first = __stl_prime_list;
const unsigned long* last = __stl_prime_list + __stl_num_primes;
const unsigned long* pos = lower_bound(first, last, n);
return pos == last ? *(last - 1) : *pos;
}
template<class T>
class HashNode
{
using Node = HashNode<T>;
public:
HashNode() = default;
HashNode(const T& kv)
:_data(kv)
{
}
T _data;
Node* _next = nullptr;
};
template<class K>
class Equal
{
public:
bool operator()(const K& k1,const K& k2)
{
return k1 == k2;
}
};
template<class K>
class HashKey
{
public:
size_t operator()(const K& k)
{
return (size_t)k;
}
};
template<>
class HashKey<string>
{
public:
size_t operator()(const string& s)
{
size_t ret = 0;
for (auto c : s)
{
ret = ret * 131 + c;
}
return ret;
}
};
template<class K, class T,class KeyOfT, class Hash, class Pred>
class HashTable
{
using Node = HashNode<T>;
public:
HashTable()
:_tables(__stl_next_prime(0))
, _n(0)
{
}
HashTable(const HashTable<K, T, KeyOfT, Hash, Pred>& ht)
:_tables(__stl_next_prime(ht._tables.size()))
, _n(0)
{
for (size_t i = 0; i < ht._tables.size(); i++)
{
Node* cur = ht._tables[i];
while (cur)
{
Insert(cur->_data);
cur = cur->_next;
}
}
}
HashTable<K, T, KeyOfT, Hash, Pred>& operator=(HashTable<K, T, KeyOfT, Hash, Pred> ht)
{
swap(ht);
return *this;
}
~HashTable()
{
for (size_t i = 0; i < _tables.size(); i++)
{
Node* cur = _tables[i];
while (cur)
{
Node* next = cur->_next;
delete cur;
cur = next;
}
_tables[i] = nullptr;
}
}
void swap(HashTable<K, T, KeyOfT, Hash, Pred>& ht)
{
std::swap(this->_tables, ht._tables);
std::swap(this->_n, ht._n);
}
bool Insert(const T& kv)
{
KeyOfT kot;
Hash hk;
if (Find(kot(kv)))
return false;
if (_n == _tables.size())
{
vector<Node*> newtables(__stl_next_prime(_tables.size() + 1), nullptr);
for (int i = 0; i < _tables.size(); i++)
{
if (_tables[i])
{
Node* cur = _tables[i];
while (cur)
{
Node* next = cur->_next;
size_t hash0 = hk(kot(cur->_data)) % newtables.size();
cur->_next = newtables[hash0];
newtables[hash0] = cur;
cur = next;
}
_tables[i] = nullptr;
}
}
_tables.swap(newtables);
}
size_t hash0 = hk(kot(kv)) % _tables.size();
Node* newnode = new Node(kv);
newnode->_next = _tables[hash0];
_tables[hash0] = newnode;
++_n;
return true;
}
bool Erase(const K& k)
{
Pred pd;
Hash hk;
size_t hash0 = hk(k) % _tables.size();
Node* cur = _tables[hash0];
Node* parent = nullptr;
while (cur)
{
KeyOfT kot;
if (pd(kot(cur->_data), k))
{
if (parent)
{
parent->_next = cur->_next;
}
else
{
_tables[hash0] = cur->_next;
}
delete cur;
--_n;
return true;
}
else
{
parent = cur;
cur = cur->_next;
}
}
return false;
}
Node* Find(const K& k)
{
Pred pd;
Hash hk;
KeyOfT kot;
size_t hash0 = hk(k) % _tables.size();
Node* cur = _tables[hash0];
while (cur)
{
if (pd(kot(cur->_data), k))
return cur;
else
cur = cur->_next;
}
return nullptr;
}
private:
vector<Node*> _tables;
size_t _n = 0;//数据个数
};
八:总结
本次重构核心目标:消除哈希表底层对上层存储类型的依赖,实现底层复用 。 初始版本哈希表硬编码存储pair<K,V>,只能服务 unordered_map,无法复用给 unordered_set。 解决方案就是 STL 广泛使用的仿函数 + 模板萃取思想:
最终版本通过三套仿函数彻底解耦业务逻辑:
- KeyOfT 键萃取仿函数:解决「单个 Key /pair 键值对」不同存储类型的取键统一问题,统一从任意存储类型 T 中提取关键字,解决 "T 可能是 Key,也可能是 pair" 的类型差异问题;
- Hash 哈希仿函数:剥离哈希算法,支持内置类型、字符串、自定义类型哈希拓展
- Pred 相等谓词仿函数 :将元素判等逻辑抽离,脱离原生
==硬编码
模板参数自上而下传递:unordered_set/unordered_map → HashTable,底层不再写死任何业务逻辑,完全开放扩展。至此通用哈希表骨架搭建完毕,下一步我们在此基础上,实现单向迭代器遍历、素数表扩容、拷贝构造、赋值重载、删除查找、map 专属 operator[] 等核心接口,完成工业级简易哈希容器复刻。
2.支持iterator实现
2.1.iterator核心源代码
cpp
template <class Value, class Key, class HashFcn,
class ExtractKey, class EqualKey, class Alloc>
struct __hashtable_iterator {
typedef hashtable<Value, Key, HashFcn, ExtractKey, EqualKey, Alloc>
hashtable;
typedef __hashtable_iterator<Value, Key, HashFcn,
ExtractKey, EqualKey, Alloc>
iterator;
typedef __hashtable_const_iterator<Value, Key, HashFcn,
ExtractKey, EqualKey, Alloc>
const_iterator;
typedef __hashtable_node<Value> node;
typedef forward_iterator_tag iterator_category;
typedef Value value_type;
node* cur;
hashtable* ht;
__hashtable_iterator(node* n, hashtable* tab) : cur(n), ht(tab) {}
__hashtable_iterator() {}
reference operator*() const { return cur->val; }
#ifndef __SGI_STL_NO_ARROW_OPERATOR
pointer operator->() const { return &(operator*()); }
#endif /* __SGI_STL_NO_ARROW_OPERATOR */
iterator& operator++();
iterator operator++(int);
bool operator==(const iterator& it) const { return cur == it.cur; }
bool operator!=(const iterator& it) const { return cur != it.cur; }
};
template <class V, class K, class HF, class ExK, class EqK, class A>
__hashtable_iterator<V, K, HF, ExK, EqK, A>&
__hashtable_iterator<V, K, HF, ExK, EqK, A>::operator++()
{
const node* old = cur;
cur = cur->next;
if (!cur) {
size_type bucket = ht->bkt_num(old->val);
while (!cur && ++bucket < ht->buckets.size())
cur = ht->buckets[bucket];
}
return *this;
}
2.2.iterator实现思路分析
- 哈希表迭代器大体实现思路和
list迭代器相近:封装结点指针,配合运算符重载模拟指针访问行为;哈希迭代器属于单向迭代器,仅支持自增++,不支持反向迭代。 - 核心难点在于
operator++:迭代器内部持有结点指针。若当前桶内还有后继结点,直接走到同桶下一个结点;如果当前桶链表遍历完毕,则需要向后寻找下一个非空桶。 实现上关键设计:迭代器除结点指针外,额外保存底层哈希表的指针。当桶内链表走到末尾时,先算出当前桶下标,从当前位置向后遍历桶数组,找到第一个存在结点的桶。 begin():遍历桶数组,找到第一个非空桶,使用桶内首结点构造迭代器;end():统一使用空结点构造迭代器作为尾后标记。unordered_set迭代器禁止修改元素:底层哈希表存储类型使用const K
cpp
HashTable<K, const K, SetKeyOfT, Hash, Pred> _ht;
unordered_map迭代器规则:key 不可修改,value 允许修改。底层存储pair<const K, V>,天然限制 key,value 可读写
cpp
HashTable<K, pair<const K, V>, MapKeyOfT, Hash, Pred> _ht;
完整可用迭代器还需要补齐大量边界细节,完整实现参考后续代码。

3.map支持\[\]
unordered_map想要实现[]运算符,核心前提是改造插入接口:将底层HashTable::Insert的返回值从bool修改为pair<Iterator, bool> Insert(const T& data)。 规则对齐标准库:- 若 key 不存在:插入新元素,返回
{新元素迭代器, true}; - 若 key 已存在:不插入,返回
{已有元素迭代器, false}。
- 若 key 不存在:插入新元素,返回
- 当 Insert 接口支持返回迭代器后,
operator[]的实现逻辑就非常清晰。具体实现参考下方代码。
4.qiu::map和qiu::set实现
Hash Table.h
cpp
#pragma once
#include <iostream>
#include <vector>
#include <string>
#include <algorithm>
using namespace std;
inline unsigned long __stl_next_prime(unsigned long n)
{
// Note: assumes long is at least 32 bits.
static const int __stl_num_primes = 28;
static const unsigned long __stl_prime_list[__stl_num_primes] =
{
53, 97, 193, 389, 769,
1543, 3079, 6151, 12289, 24593,
49157, 98317, 196613, 393241, 786433,
1572869, 3145739, 6291469, 12582917, 25165843,
50331653, 100663319, 201326611, 402653189, 805306457,
1610612741, 3221225473, 4294967291
};
const unsigned long* first = __stl_prime_list;
const unsigned long* last = __stl_prime_list + __stl_num_primes;
const unsigned long* pos = lower_bound(first, last, n);
return pos == last ? *(last - 1) : *pos;
}
template<class T>
class HashNode
{
using Node = HashNode<T>;
public:
HashNode() = default;
HashNode(const T& kv)
:_data(kv)
{
}
T _data;
Node* _next = nullptr;
};
template<class K>
class Equal
{
public:
bool operator()(const K& k1,const K& k2)
{
return k1 == k2;
}
};
template<class K>
class HashKey
{
public:
size_t operator()(const K& k)
{
return (size_t)k;
}
};
template<>
class HashKey<string>
{
public:
size_t operator()(const string& s)
{
size_t ret = 0;
for (auto c : s)
{
ret = ret * 131 + c;
}
return ret;
}
};
template<class K, class T, class KeyOfT, class Hash, class Pred>
class HashTable;
template<class K, class T, class KeyOfT, class Hash, class Pred,class Ptr,class Ref>
class HashIterator
{
using Node = HashNode<T>;
using hash = HashTable<K, T, KeyOfT, Hash, Pred>;
using Self = HashIterator< K, T, KeyOfT, Hash, Pred, Ptr, Ref>;
public:
HashIterator(Node* node,const hash* ht)
:_node(node)
, _ht(ht)
{
}
Ref operator*()
{
return _node->_data;
}
Ptr operator->()
{
return &_node->_data;
}
bool operator!=(const Self& it)
{
return _node != it._node;
}
Self& operator++()
{
if (_node->_next)
{
_node = _node->_next;
}
else
{
Hash hs;
KeyOfT kot;
size_t hash0 = hs(kot(_node->_data)) % _ht->_tables.size();
++hash0;
while (hash0 < _ht->_tables.size())
{
if (_ht->_tables[hash0])
break;
++hash0;
}
if (hash0 == _ht->_tables.size())
_node = nullptr;
else
_node = _ht->_tables[hash0];
}
return *this;
}
Self operator++(int)
{
Self s = *this;
++(*this);
return s;
}
private:
Node* _node = nullptr;
const hash* _ht = nullptr;
};
template<class K, class T,class KeyOfT, class Hash, class Pred>
class HashTable
{
using Node = HashNode<T>;
template<class K, class T, class KeyOfT, class Hash, class Pred, class Ptr, class Ref>
friend class HashIterator;
public:
using Iterator = HashIterator<K, T, KeyOfT, Hash, Pred, T*, T&>;
using ConstIterator = HashIterator<K, T, KeyOfT, Hash, Pred, const T*, const T&>;
Iterator Begin()
{
if (_n == 0)
return End();
size_t hashi = 0;
while (_tables[hashi] == nullptr)
{
++hashi;
}
return Iterator(_tables[hashi], this);
}
Iterator End()
{
return Iterator(nullptr, this);
}
ConstIterator Begin() const
{
if (_n == 0)
return End();
size_t hashi = 0;
while (_tables[hashi] == nullptr)
{
++hashi;
}
return ConstIterator(_tables[hashi], this);
}
ConstIterator End() const
{
return ConstIterator(nullptr, this);
}
HashTable()
:_tables(__stl_next_prime(0))
, _n(0)
{
}
HashTable(const HashTable<K, T, KeyOfT, Hash, Pred>& ht)
:_tables(__stl_next_prime(ht._tables.size()))
, _n(0)
{
for (size_t i = 0; i < ht._tables.size(); i++)
{
Node* cur = ht._tables[i];
while (cur)
{
Insert(cur->_data);
cur = cur->_next;
}
}
}
HashTable<K, T, KeyOfT, Hash, Pred>& operator=(HashTable<K, T, KeyOfT, Hash, Pred> ht)
{
swap(ht);
return *this;
}
~HashTable()
{
for (size_t i = 0; i < _tables.size(); i++)
{
Node* cur = _tables[i];
while (cur)
{
Node* next = cur->_next;
delete cur;
cur = next;
}
_tables[i] = nullptr;
}
}
void swap(HashTable<K, T, KeyOfT, Hash, Pred>& ht)
{
std::swap(this->_tables, ht._tables);
std::swap(this->_n, ht._n);
}
pair<Iterator, bool> Insert(const T& kv)
{
KeyOfT kot;
Hash hk;
Iterator it = Find(kot(kv));
if (it != End())
return { it,false };
if (_n == _tables.size())
{
vector<Node*> newtables(__stl_next_prime(_tables.size() + 1), nullptr);
for (int i = 0; i < _tables.size(); i++)
{
if (_tables[i])
{
Node* cur = _tables[i];
while (cur)
{
Node* next = cur->_next;
size_t hash0 = hk(kot(cur->_data)) % newtables.size();
cur->_next = newtables[hash0];
newtables[hash0] = cur;
cur = next;
}
_tables[i] = nullptr;
}
}
_tables.swap(newtables);
}
size_t hash0 = hk(kot(kv)) % _tables.size();
Node* newnode = new Node(kv);
newnode->_next = _tables[hash0];
_tables[hash0] = newnode;
++_n;
return { Iterator(newnode,this),true };
}
bool Erase(const K& k)
{
Pred pd;
Hash hk;
size_t hash0 = hk(k) % _tables.size();
Node* cur = _tables[hash0];
Node* parent = nullptr;
while (cur)
{
KeyOfT kot;
if (pd(kot(cur->_data), k))
{
if (parent)
{
parent->_next = cur->_next;
}
else
{
_tables[hash0] = cur->_next;
}
delete cur;
--_n;
return true;
}
else
{
parent = cur;
cur = cur->_next;
}
}
return false;
}
Iterator Find(const K& k)
{
Pred pd;
Hash hk;
KeyOfT kot;
size_t hash0 = hk(k) % _tables.size();
Node* cur = _tables[hash0];
while (cur)
{
if (pd(kot(cur->_data), k))
return Iterator(cur, this);
else
cur = cur->_next;
}
return Iterator(nullptr, this);
}
private:
vector<Node*> _tables;
size_t _n = 0;//数据个数
};
unordered_map.h
cpp
#pragma once
#include "Hash Table.h"
namespace qiu
{
template<class K,class V, class Hash = HashKey<K>, class Pred = Equal<K>>
class unordered_map
{
struct MapKeyOfT
{
const K& operator()(const pair<const K, V>& kv)
{
return kv.first;
}
};
using ht = HashTable<K, pair<const K, V>, MapKeyOfT, Hash, Pred>;
public:
using iterator = typename HashTable<K, pair<const K, V>, MapKeyOfT, Hash, Pred>::Iterator;
using const_iterator = typename HashTable<K, pair<const K, V>, MapKeyOfT, Hash, Pred>::ConstIterator;
iterator begin()
{
return _ht.Begin();
}
iterator end()
{
return _ht.End();
}
const_iterator begin() const
{
return _ht.Begin();
}
const_iterator end() const
{
return _ht.End();
}
pair<iterator, bool> insert(const pair<const K, V>& kv)
{
return _ht.Insert(kv);
}
V& operator[](const K& k)
{
pair<iterator, bool> ret = _ht.Insert({ k,V() });
return ret.first->second;
}
bool erase(const K& k)
{
return _ht.Erase(k);
}
iterator find(const K& key)
{
return _ht.Find(key);
}
private:
ht _ht;
};
}
unordered_set.h
cpp
#pragma once
#include "Hash Table.h"
namespace qiu
{
template<class K,class Hash = HashKey<K>,class Pred = Equal<K>>
class unordered_set
{
struct SetKeyOfT
{
const K& operator()(const K& key)
{
return key;
}
};
using ht = HashTable<K,const K, SetKeyOfT, Hash, Pred>;
public:
using iterator = typename HashTable<K,const K, SetKeyOfT, Hash, Pred>::Iterator;
using const_iterator = typename HashTable<K,const K, SetKeyOfT, Hash, Pred>::ConstIterator;
iterator begin()
{
return _ht.Begin();
}
iterator end()
{
return _ht.End();
}
const_iterator begin() const
{
return _ht.Begin();
}
const_iterator end() const
{
return _ht.End();
}
pair<iterator, bool> insert(const K& kv)
{
return _ht.Insert(kv);
}
bool erase(const K& k)
{
return _ht.Erase(k);
}
iterator find(const K& key)
{
return _ht.Find(key);
}
private:
ht _ht;
};
}
test.h
cpp
#define _CRT_SECURE_NO_WARNINGS
#include <iostream>
#include <string>
#include "unordered_map.h"
#include "unordered_set.h"
using namespace std;
int main()
{
qiu::unordered_map<int, int> mp;
qiu::unordered_set<string> st;
mp.insert({ 1,1 });
mp[2] = 2;
mp.insert({ 3,3 });
mp[4];
qiu::unordered_map<int, int> mp1;
st.insert("1");
st.insert("2");
st.insert("33");
st.insert("44");
qiu::unordered_set<string> st1;
mp1 = mp;
st1 = st;
mp1[4] = 4;
for (auto x : mp)
cout << x.first << "->" << x.second << " ";
cout << endl;;
for (auto x : mp1)
cout << x.first << "->" << x.second << " ";
cout << endl;;
auto it = st.begin();
while (it != st.end())
{
cout << *it << " ";
++it;
}
return 0;
}
结语:
至此,我们从源码溯源、原理剖析、分步重构、迭代器实现、接口补齐,完整实现了 C++ unordered_set 与 unordered_map 的底层哈希表模拟实现。 从硬编码耦合版本,到完全解耦、可拓展、贴近标准库的通用哈希表,层层递进吃透了哈希容器的全部核心考点。 掌握这套架构,不仅能彻底理解无序容器的底层机制,也能为后续学习自定义哈希、解决哈希冲突、实现多值哈希容器(multiset/multimap)打下扎实基础。
那么,C++ unordered_set和unordered_map底层实现部分的内容就全部讲解完毕啦,希望以上内容对你有所帮助,感谢观看,若觉得写的还可以,可以分享给朋友一起来看哦,毕竟一起进步更有动力嘛,当然能关注一下就更好啦。
