跳到主要内容C++ STL set 与 map 底层封装原理详解 | 极客日志C++算法
C++ STL set 与 map 底层封装原理详解
本文深入解析了 C++ STL 中 set 和 map 的底层封装原理。两者均基于红黑树实现,通过泛型模板参数适配不同的存储类型。文章详细阐述了如何修改红黑树节点定义以支持 Key 与 Key-Value 对,利用仿函数提取比较键值,以及迭代器在普通与 const 场景下的复用机制。重点讲解了 set 强制 const 迭代器、map 允许 Value 修改但锁定 Key 的设计细节,以及 operator[] 重载如何通过插入返回值实现自动创建功能。内容涵盖红黑树旋转调整、迭代器中序遍历逻辑及最终的核心代码实现,适合希望深入理解 STL 源码的开发者阅读。
灰度发布0 浏览 C++ STL set 与 map 底层封装原理详解
在深入理解 C++ STL 容器之前,我们通常知道 set 和 map 的底层实现都依赖于红黑树。但具体是如何通过模板机制将同一棵红黑树适配到这两种不同语义的容器上的?本章我们将拆解其封装细节。
一、认识 STL 中的 set 和 map
从接口上看,set 只接受一个类型参数(Key),而 map 接受两个(Key-Value)。但在库的实现层面,它们本质上都是基于 Key-Value 类型的红黑树构建的。
- set:虽然对外表现为
set<Key>,内部实际是通过 rb_tree<Key, Key> 封装实现的。
- map:对外表现为
map<Key, Value>,内部实际是通过 rb_tree<Key, pair<Key, Value>> 封装实现的。
这意味着我们需要设计一种通用的红黑树结构,能够根据传入的第二个模板参数灵活存储数据,同时保证查找逻辑始终基于 Key 进行。
二、修改红黑树以适应泛型存储
为了支持 set 和 map 的不同需求,红黑树的节点定义需要更加通用。
1. 节点定义
节点模板中引入泛型 T,使其既能存储 Key(用于 set),也能存储 pair<Key, Value>(用于 map)。
template<class T> struct RBTreeNode {
RBTreeNode<T>* _left;
RBTreeNode<T>* _right;
RBTreeNode<T>* _parent;
enum Colour { RED, BLACK };
Colour _col;
T _data;
RBTreeNode(const T& data) :_left(nullptr), _right(nullptr), _parent(nullptr), _data(data), _col(RED) {}
};
2. 插入操作与 Key 提取策略
由于 set 和 map 比较的对象不同(一个是 Key,一个是 Pair 的 first),我们需要引入第三个模板参数 KeyOfT,这是一个仿函数,负责从存储的数据中提取出用于比较的 Key。
- Set 场景:
KeyOfT 直接返回 Key 本身。
- Map 场景:
KeyOfT 返回 pair.first。
这样,红黑树的插入逻辑就可以统一为:利用 KeyOfT 获取当前节点的 Key 值进行比较。
template<class K, , > {
RBTreeNode<T> Node;
:
{
(_root == ) {
_root = (data);
_root->_col = BLACK;
;
}
KeyOfT kot;
Node* cur = _root;
Node* parent = ;
(cur) {
((cur->_data) < (data)) {
parent = cur;
cur = cur->_right;
} ((cur->_data) > (data)) {
parent = cur;
cur = cur->_left;
} {
;
}
}
cur = (data);
((parent->_data) < (data)) {
parent->_right = cur;
} {
parent->_left = cur;
}
cur->_parent = parent;
(parent && parent->_col == RED) {
Node* grandparent = parent->_parent;
(parent == grandparent->_left) {
Node* uncle = grandparent->_right;
(uncle && uncle->_col == RED) {
parent->_col = uncle->_col = BLACK;
grandparent->_col = RED;
cur = grandparent;
parent = cur->_parent;
} {
(cur == parent->_right) {
(parent);
std::(parent, cur);
}
(grandparent);
parent->_col = BLACK;
grandparent->_col = RED;
;
}
} {
Node* uncle = grandparent->_left;
(uncle && uncle->_col == RED) {
parent->_col = uncle->_col = BLACK;
grandparent->_col = RED;
cur = grandparent;
parent = cur->_parent;
} {
(cur == parent->_left) {
(parent);
std::(parent, cur);
}
(grandparent);
parent->_col = BLACK;
grandparent->_col = RED;
;
}
}
}
_root->_col = BLACK;
;
}
:
Node* _root = ;
;
;
};
class
T
class
KeyOfT
class
RBTree
typedef
public
bool Insert(const T& data)
if
nullptr
new
Node
return
true
nullptr
while
if
kot
kot
else
if
kot
kot
else
return
false
new
Node
if
kot
kot
else
while
if
if
else
if
RotateL
swap
RotateR
break
else
if
else
if
RotateR
swap
RotateL
break
return
true
private
nullptr
void RotateL(Node* parent)
void RotateR(Node* parent)
三、红黑树迭代器的实现
迭代器是遍历容器的关键。我们需要区分普通迭代器和 const 迭代器,这可以通过模板参数控制返回值类型来实现。
1. 迭代器定义
使用三个模板参数:T(数据类型)、Ref(引用类型)、Ptr(指针类型)。
template<class T, class Ref, class Ptr>
struct __TreeIterator {
typedef RBTreeNode<T> Node;
Node* _node;
typedef __TreeIterator Self;
__TreeIterator(Node* node) :_node(node) {}
Ref operator*() { return _node->_data; }
Ptr operator->() { return &_node->_data; }
bool operator!=(const Self& s) const { return _node != s._node; }
bool operator==(const Self& s) const { return _node == s._node; }
};
2. 自增与自减逻辑
- ++:若右子树非空,找右子树最左节点;否则向上找第一个作为父节点左孩子的祖先。
- --:若左子树非空,找左子树最右节点;否则向上找第一个作为父节点右孩子的祖先。
3. 普通与 Const 迭代器
typedef __TreeIterator<T, T&, T*> iterator;
typedef __TreeIterator<T, const T&, const T*> const_iterator;
四、set 和 map 的封装细节
1. 迭代器的封装
Set 的特殊性:set 中的元素不可修改,因此其 iterator 本质上应等同于 const_iterator。
template<class K> class set {
struct SetKeyOfT {
const K& operator()(const K& key) { return key; }
};
public:
typedef typename RBTree<K, K, SetKeyOfT>::const_iterator iterator;
typedef typename RBTree<K, K, SetKeyOfT>::const_iterator const_iterator;
iterator begin() const { return _t.begin(); }
iterator end() const { return _t.end(); }
private:
RBTree<K, K, SetKeyOfT> _t;
};
注意这里 begin() 必须加 const,因为内部树对象 _t 在 const 上下文中访问时,只能调用 const 版本的 begin。
Map 的特殊性:map 的 Key 不可变,Value 可变。因此存储类型为 pair<const K, V>。
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>::const_iterator const_iterator;
iterator begin() { return _t.begin(); }
iterator end() { return _t.end(); }
private:
RBTree<K, pair<const K, V>, MapKeyOfT> _t;
};
2. map 的 [] 重载实现
operator[] 的行为是:如果 Key 存在,返回对应 Value 的引用;如果不存在,插入默认构造的 Value 并返回引用。
这需要修改 Insert 函数的返回值,使其返回 pair<iterator, bool>,指示插入是否成功以及位置。
V& operator[](const K& key) {
pair<iterator, bool> ret = insert(make_pair(key, V()));
return ret.first->second;
}
3. 插入操作的封装
对于 set,由于内部迭代器被强制为 const 类型,而 Insert 返回的是普通迭代器,需要进行类型转换或包装。
pair<iterator, bool> insert(const K& key) {
pair<typename RBTree<K, K, SetKeyOfT>::iterator, bool> ret = _t.Insert(key);
return pair<iterator, bool>(ret.first, ret.second);
}
五、核心代码汇总
以下是整合后的红黑树及容器核心实现片段,供参考调试。
#pragma once
#include <utility>
enum Colour { RED, BLACK };
template<class T> struct RBTreeNode {
RBTreeNode<T>* _left;
RBTreeNode<T>* _right;
RBTreeNode<T>* _parent;
Colour _col;
T _data;
RBTreeNode(const T& data) :_left(nullptr), _right(nullptr), _parent(nullptr), _data(data), _col(RED) {}
};
template<class T, class Ref, class Ptr>
struct __TreeIterator {
typedef RBTreeNode<T> Node;
Node* _node;
typedef __TreeIterator Self;
__TreeIterator(Node* node) :_node(node) {}
Ref operator*() { return _node->_data; }
Ptr operator->() { return &_node->_data; }
bool operator!=(const Self& s) const { return _node != s._node; }
bool operator==(const Self& s) const { return _node == s._node; }
Self& operator++() {
if (_node->_right) {
Node* cur = _node->_right;
while (cur->_left) cur = cur->_left;
_node = cur;
} else {
Node* cur = _node;
Node* parent = cur->_parent;
while (parent && cur == parent->_right) {
cur = parent;
parent = cur->_parent;
}
_node = parent;
}
return *this;
}
Self& operator--() {
if (_node->_left) {
Node* cur = _node->_left;
while (cur->_right) cur = cur->_right;
_node = cur;
} else {
Node* cur = _node;
Node* parent = cur->_parent;
while (parent && cur == parent->_left) {
cur = parent;
parent = cur->_parent;
}
_node = parent;
}
return *this;
}
};
template<class K, class T, class KeyOfT>
class RBTree {
typedef RBTreeNode<T> Node;
public:
typedef __TreeIterator<T, T&, T*> iterator;
typedef __TreeIterator<T, const T&, const T*> const_iterator;
iterator begin() {
Node* cur = _root;
while (cur && cur->_left) cur = cur->_left;
return iterator(cur);
}
iterator end() { return iterator(nullptr); }
const_iterator begin() const {
Node* cur = _root;
while (cur && cur->_left) cur = cur->_left;
return const_iterator(cur);
}
const_iterator end() const { return const_iterator(nullptr); }
pair<iterator, bool> Insert(const T& data) {
if (!_root) {
_root = new Node(data);
_root->_col = BLACK;
return make_pair(iterator(_root), true);
}
KeyOfT kot;
Node* cur = _root;
Node* parent = nullptr;
while (cur) {
if (kot(cur->_data) < kot(data)) {
parent = cur;
cur = cur->_right;
} else if (kot(cur->_data) > kot(data)) {
parent = cur;
cur = cur->_left;
} else {
return make_pair(iterator(cur), false);
}
}
cur = new Node(data);
if (kot(parent->_data) < kot(data)) parent->_right = cur;
else parent->_left = cur;
cur->_parent = parent;
_root->_col = BLACK;
return make_pair(iterator(cur), true);
}
private:
Node* _root = nullptr;
void RotateL(Node* parent) {}
void RotateR(Node* parent) {}
};
#pragma once
#include "RBTree.h"
namespace MyCreate {
template<class K> class set {
struct SetKeyOfT {
const K& operator()(const K& key) { return key; }
};
public:
typedef typename RBTree<K, K, SetKeyOfT>::const_iterator iterator;
typedef typename RBTree<K, K, SetKeyOfT>::const_iterator const_iterator;
iterator begin() const { return _t.begin(); }
iterator end() const { return _t.end(); }
pair<iterator, bool> insert(const K& key) {
pair<typename RBTree<K, K, SetKeyOfT>::iterator, bool> ret = _t.Insert(key);
return pair<iterator, bool>(ret.first, ret.second);
}
private:
RBTree<K, K, SetKeyOfT> _t;
};
}
#pragma once
#include "RBTree.h"
namespace MyCreate {
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>::const_iterator 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); }
V& operator[](const K& key) {
pair<iterator, bool> ret = insert(make_pair(key, V()));
return ret.first->second;
}
private:
RBTree<K, pair<const K, V>, MapKeyOfT> _t;
};
}
通过上述封装,我们不仅复用了红黑树结构,还通过模板元编程实现了类型安全与语义隔离。在实际工程中,理解这些底层机制有助于我们在遇到性能瓶颈或内存问题时做出更精准的优化决策。
相关免费在线工具
- 加密/解密文本
使用加密算法(如AES、TripleDES、Rabbit或RC4)加密和解密文本明文。 在线工具,加密/解密文本在线工具,online
- Gemini 图片去水印
基于开源反向 Alpha 混合算法去除 Gemini/Nano Banana 图片水印,支持批量处理与下载。 在线工具,Gemini 图片去水印在线工具,online
- Base64 字符串编码/解码
将字符串编码和解码为其 Base64 格式表示形式即可。 在线工具,Base64 字符串编码/解码在线工具,online
- Base64 文件转换器
将字符串、文件或图像转换为其 Base64 表示形式。 在线工具,Base64 文件转换器在线工具,online
- Markdown转HTML
将 Markdown(GFM)转为 HTML 片段,浏览器内 marked 解析;与 HTML转Markdown 互为补充。 在线工具,Markdown转HTML在线工具,online
- HTML转Markdown
将 HTML 片段转为 GitHub Flavored Markdown,支持标题、列表、链接、代码块与表格等;浏览器内处理,可链接预填。 在线工具,HTML转Markdown在线工具,online