前言
STL 的 map 和 set 是高频使用的关联式容器,其底层都依赖红黑树实现。本文结合源码框架与实现细节,从红黑树的泛型改造入手,一步步拆解 myMap 和 mySet 的封装逻辑,包括关键的仿函数设计、迭代器实现、key 不可修改约束及 map 的 [] 运算符重载,全程附可运行代码。
一. 架构与实现:总览设计框架
SGI-STL30 版本源代码中,map 和 set 的实现框架核心部分如下:
template<class Key, class Compare = less<Key>, class Alloc = alloc>
class set {
public:
typedef Key key_type;
typedef Key value_type;
private:
typedef rb_tree<key_type, value_type, identity<value_type>, key_compare, Alloc> rep_type;
rep_type t;
};
template<class Key, class T, class Compare = less<Key>, class Alloc = alloc>
class map {
public:
typedef Key key_type;
typedef T mapped_type;
typedef pair<const Key, T> value_type;
private:
typedef rb_tree<key_type, value_type, select1st<value_type>, key_compare, Alloc> rep_type;
rep_type t;
};
struct __rb_tree_node_base {
typedef __rb_tree_color_type color_type;
typedef __rb_tree_node_base* base_ptr;
color_type color;
base_ptr parent;
base_ptr left;
base_ptr right;
};
template<class Value>
struct __rb_tree_node : public __rb_tree_node_base {
typedef __rb_tree_node<Value>* link_type;
Value value_field;
};
通过框架分析可以看到,rb_tree 用了一个巧妙的泛型思想实现。rb_tree 实现 key 的搜索场景还是 key/value 的搜索场景不是直接写死的,而是由第二个模板参数 Value 决定 _rb_tree_node 中存储的数据类型。
- set 实例化
rb_tree 时第二个模板参数给的是 Key。
- map 实例化
rb_tree 时第二个模板参数给的是 pair<const Key, T>。
这样一颗红黑树既可以实现 key 搜索场景,也可以实现 key/value 搜索场景的 map。
rb_tree 第二个模板参数 Value 已经控制了红黑树结点中的存储的数据类型,为什么还要传第一个模板参数 Key?对于 map 和 set,find/erase 时的函数参数都是 Key,所以第一个模板参数是传给 find/erase 等函数做形参的类型的。对于 set 而言两个参数是一样的,但是对于 map 而言就完全不一样了,map insert 的是 pair 对象,但是 find 和 erase 的 Key 对象。
二. 核心设计思路:红黑树的泛型复用
STL 中 map 和 set 复用同一颗红黑树的核心是泛型编程 + 仿函数提取 key,解决了'一颗树适配两种数据场景'的问题。
2.1 红黑树的模板参数设计
红黑树需要支持存储两种数据类型:
- set 场景:存储单个 key(如 int、string);
- map 场景:存储 pair<const Key, Value>(key 不可修改)。
因此红黑树的模板参数需抽象为 3 个:
template<class K, class T, class KeyOfT>
class RBTree {
};
2.2 仿函数 KeyOfT:统一 key 提取逻辑
由于 T 的类型不固定(K 或 pair),红黑树插入 / 查找时无法直接获取 key,需通过仿函数 KeyOfT 统一提取,由 map 和 set 分别实现适配:
- set 的仿函数:直接返回 key(T=K);
- map 的仿函数:返回 pair 的 first 成员(T=pair<const K, V>)。
2.3 核心约束:key 不可修改
- set 的 key 是唯一标识,需禁止修改:红黑树存储 const K;
- map 的 key 是索引,需禁止修改:pair 的 first 设为 const K,value 可正常修改。
三. 基础组件实现:红黑树与仿函数
3.1 红黑树节点结构
节点存储模板类型 T,包含左右子指针、父指针和颜色标记:
#pragma once
#include <iostream>
#include <assert.h>
using namespace std;
enum Colour { Red, Black };
template<class T>
struct RBTreeNode {
T _data;
RBTreeNode<T>* _parent;
RBTreeNode<T>* _left;
RBTreeNode<T>* _right;
Colour _col;
RBTreeNode(const T& data) : _parent(nullptr), _left(nullptr), _right(nullptr), _data(data), _col(Red) {}
};
3.2 仿函数实现(map/set 层)
3.2.1 set 的仿函数:直接返回 key
#pragma once
#include "RBTree.h"
namespace MySTL {
template<class K>
class set {
struct SetKeyofT {
const K& operator()(const K& key) { return key; }
};
private:
RBTree<K, const K, SetKeyofT> _t;
};
}
3.2.2 map 的仿函数:提取 pair 的 first
#pragma once
#include "RBTree.h"
namespace MySTL {
template<class K, class V>
class map {
struct MapKeyofT {
const K& operator()(const pair<K, V>& kv) { return kv.first; }
};
private:
RBTree<K, pair<const K, V>, MapKeyofT> _t;
};
}
3.3 红黑树核心接口(附迭代器)
重点实现 Insert(返回 pair<Iterator, bool>,支持 map 的 [])和 Find,平衡维护逻辑与基础红黑树一致:
#pragma once
#include <iostream>
#include <assert.h>
using namespace std;
enum Colour { Red, Black };
template<class T>
struct RBTreeNode {
T _data;
RBTreeNode<T>* _parent;
RBTreeNode<T>* _left;
RBTreeNode<T>* _right;
Colour _col;
RBTreeNode(const T& data) : _parent(nullptr), _left(nullptr), _right(nullptr), _data(data), _col(Red) {}
};
template<class T, class Ref, class Ptr>
struct RBTreeIterator {
typedef RBTreeNode<T> Node;
typedef RBTreeIterator<T, Ref, Ptr> Self;
Node* _node;
RBTreeIterator(Node* node) : _node(node) {}
Self& operator++() {
if (_node->_right) {
Node* minRight = _node->_right;
while (minRight->_left) minRight = minRight->_left;
_node = minRight;
} else {
Node* cur = _node;
Node* parent = cur->_parent;
while (parent && cur == parent->_right) {
cur = parent;
parent = parent->_parent;
}
_node = parent;
}
return *this;
}
Ref operator*() { return _node->_data; }
Ptr operator->() { &(_node->_data); }
!=( Self& s) { _node != s._node; }
};
< , , >
{
RBTreeNode<T> Node;
:
RBTreeIterator<T, T&, T*> Iterator;
RBTreeIterator<T, T&, T*> ConstIterator;
~() { (_root); _root = ; }
{
(root == ) ;
(root->_left);
(root->_right);
root;
}
{
Node* minLeft = _root;
(minLeft && minLeft->_left) minLeft = minLeft->_left;
(minLeft);
}
{ (); }
{
(_root == ) {
_root = (data);
_root->_col = Black;
{ (_root), };
}
KeyofT kot;
Node* parent = ;
Node* cur = _root;
(cur) {
((cur->_data) < (data)) {
parent = cur;
cur = cur->_right;
} ((data) < (cur->_data)) {
parent = cur;
cur = cur->_left;
} {
{ (cur), };
}
}
cur = (data);
Node* newnode = cur;
cur->_col = Red;
((parent->_data) < (data)) {
parent->_right = cur;
} {
parent->_left = cur;
}
cur->_parent = parent;
(parent && parent->_col == Red) {
Node* grandparent = parent->_parent;
(grandparent->_left == parent) {
Node* uncle = grandparent->_right;
(uncle && uncle->_col == Red) {
parent->_col = Black;
uncle->_col = Black;
grandparent->_col = Red;
cur = grandparent;
parent = cur->_parent;
} {
(cur == parent->_left) {
(grandparent);
parent->_col = Black;
grandparent->_col = Red;
} {
(parent);
(grandparent);
cur->_col = Black;
grandparent->_col = Red;
}
;
}
} {
Node* uncle = grandparent->_left;
(uncle && uncle->_col == Red) {
uncle->_col = Black;
parent->_col = Black;
grandparent->_col = Red;
cur = grandparent;
parent = cur->_parent;
} {
(parent->_right == cur) {
(grandparent);
parent->_col = Black;
grandparent->_col = Red;
} {
(parent);
(grandparent);
cur->_col = Black;
grandparent->_col = Red;
}
;
}
}
}
_root->_col = Black;
{ (newnode), };
}
{
KeyofT kot;
Node* cur = _root;
(cur) {
((cur->_data) < key) {
cur = cur->_right;
} ((cur->_data) > key) {
cur = cur->_left;
} {
(cur);
}
}
();
}
:
{
Node* subL = parent->_left;
Node* subLR = subL->_right;
parent->_left = subLR;
(subLR) subLR->_parent = parent;
Node* grandparent = parent->_parent;
subL->_right = parent;
parent->_parent = subL;
(parent == _root) {
_root = subL;
subL->_parent = ;
} {
(grandparent->_left == parent) grandparent->_left = subL;
grandparent->_right = subL;
subL->_parent = grandparent;
}
}
{
Node* subR = parent->_right;
Node* subRL = subR->_left;
parent->_right = subRL;
(subRL) subRL->_parent = parent;
Node* grandparent = parent->_parent;
subR->_left = parent;
parent->_parent = subR;
(_root == parent) {
_root = subR;
subR->_parent = ;
} {
(grandparent->_left == parent) grandparent->_left = subR;
grandparent->_right = subR;
subR->_parent = grandparent;
}
}
:
Node* _root = ;
};
3.4 iterator 实现思路分析
- iterator 实现的大框架跟 list 的 iterator 思路是一致的,用一个类型封装结点的指针,再通过重载运算符实现。
- 迭代器 ++ 的核心逻辑就是只看局部,只考虑当前中序局部要访问的下一个结点。
- 如果 it 指向的结点的右子树不为空,找右子树的最左结点。
- 如果 it 指向的结点的右子树空,沿着祖先路径向上找,直到找到孩子是父亲左的那个祖先。
- end() 用 nullptr 表示,当 ++it 找不到祖先时置为 nullptr。
- set 的 iterator 不支持修改,将第二个模板参数改成 const K。
- map 的 iterator 不支持修改 key 但是可以修改 value,将 map 的第二个模板参数 pair 的第一个参数改成 const K。
四. mySet 与 myMap 完整实现
4.1 mySet 实现
#pragma once
#include "RBTree.h"
namespace MySTL {
template<class K>
class set {
struct SetKeyofT {
const K& operator()(const K& key) { return key; }
};
public:
typedef typename RBTree<K, const K, SetKeyofT>::Iterator iterator;
typedef typename RBTree<K, const K, SetKeyofT>::ConstIterator const_iterator;
iterator begin() { return _t.Begin(); }
iterator end() { return _t.End(); }
pair<iterator, bool> insert(const K& key) { return _t.Insert(key); }
iterator find(const K& key) { return _t.Find(key); }
private:
RBTree<K, const K, SetKeyofT> ;
};
}
4.2 myMap 实现
#pragma once
#include "RBTree.h"
namespace MySTL {
template<class K, class V>
class map {
struct MapKeyofT {
const K& operator()(const pair<K, V>& kv) { return kv.first; }
};
public:
typedef typename RBTree<K, pair<const K, V>, MapKeyofT>::Iterator iterator;
typedef typename RBTree<K, pair<const K, V>, MapKeyofT>::ConstIterator const_iterator;
iterator begin() { return _t.Begin(); }
iterator end() { return _t.End(); }
pair<iterator, bool> insert(const pair<K, V>& kv) { return _t.Insert(kv); }
iterator find(const K& key) { return _t.Find(key); }
V& []( K& key) {
[it, flag] = .({ key, () });
it->second;
}
:
RBTree<K, pair< K, V>, MapKeyofT> ;
};
}
五. 测试代码:验证功能
#define _CRT_SECURE_NO_WARNINGS 1
#include "RBTree.h"
#include "Map.h"
#include "Set.h"
void test_set() {
MySTL::set<int> s;
s.insert(1); s.insert(2); s.insert(5); s.insert(0); s.insert(10);
MySTL::set<int>::iterator it = s.begin();
while (it != s.end()) {
cout << *it << " ";
++it;
}
cout << endl;
}
void test_map() {
MySTL::map<string, string> dict;
dict.insert({"sort", "排序"});
dict["string"] = "字符串";
auto it = dict.begin();
while (it != dict.end()) {
cout << it->first << ":" << it->second << endl;
++it;
}
cout << endl;
}
int main() {
cout << "测试 set:" << endl;
test_set();
cout << "------------------" << endl;
cout << "测试 map:" << endl;
();
;
}
结语
myMap 和 mySet 的封装核心是红黑树的泛型复用:通过模板参数 T 适配不同数据类型,用仿函数 KeyOfT 统一 key 提取逻辑,再通过迭代器封装实现双向遍历。其中,key 不可修改的约束、map 的 [] 运算符重载、迭代器的 ++/-- 实现,都是 STL 容器设计的经典细节。掌握这套封装逻辑,不仅能理解 STL 容器的底层实现,更能学会'泛型编程 + 接口抽象'的设计思想。