跳到主要内容
STL 源码剖析:深入理解 list 容器与核心算法实现 | 极客日志
C++ 算法
STL 源码剖析:深入理解 list 容器与核心算法实现 基于 SGI STL 源码深度解析 std::list 容器。涵盖双向循环链表节点设计、双向迭代器重载逻辑、哨兵位机制及内存分配策略。重点剖析 insert、erase、splice 等核心接口的指针操作细节,以及自底向上归并排序的实现原理。适合具备 C++ 模板基础及数据结构知识的开发者阅读,旨在通过源码理解 STL 底层优化与算法思想。
STL 源码剖析:深入理解 list 容器与核心算法实现
本文基于 SGI STL 版本,从源码层面剖析 std::list 的实现细节。适合具备 C++ 模板基础、熟悉内存管理且希望深入理解 STL 底层机制的开发者阅读。
一、list 概述
相较于 vector,list 在头插、头删及任意位置插入删除操作上均能达到 O(1) 复杂度。其节点独立分配,非连续线性存储,因此不会像 vector 那样因扩容导致迭代器失效(除非显式删除了节点)。当然,代价是失去了随机访问能力,且每个节点需额外维护两个指针,内存开销略高,缓存局部性也较差。
二、list 的节点设计
list 的节点与容器本身分离设计。SGI STL 中的节点结构如下:
template <class T >
struct __list_node {
typedef void * void_pointer;
void_pointer next;
void_pointer prev;
T data;
};
可以看出,这是一个双向链表的基础结构。
三、list 迭代器
由于节点不连续,普通指针无法胜任迭代任务。list 采用双向迭代器(Bidirectional Iterator),通过重载运算符实现节点跳转。
3.1 定义
迭代器内部仅保存当前节点的地址:
template <class T , class Ref , class Ptr >
struct __list_iterator {
typedef __list_iterator<T, T&, T*> iterator;
typedef __list_iterator<T, const T&, const T*> const_iterator;
typedef __list_iterator<T, Ref, Ptr> self;
typedef T value_type;
typedef Ptr pointer;
typedef Ref reference;
typedef __list_node<T>* link_type;
typedef size_t size_type;
difference_type;
link_type node;
};
typedef
ptrdiff_t
3.2 构造 构造函数相对直观,支持默认构造、节点地址构造及拷贝构造:
__list_iterator(link_type x) : node (x) {}
__list_iterator() {}
__list_iterator(const iterator& x) : node (x.node) {}
3.3 重载 比较操作直接比对节点地址,自增/自减则更新节点指针:
template <class T , class Ref , class Ptr >
struct __list_iterator {
typedef __list_node<T>* link_type;
link_type node;
bool operator ==(const self& x) const { return node == x.node; }
bool operator !=(const self& x) const { return node != x.node; }
reference operator *() const { return (*node).data; }
#ifndef __SGI_STL_NO_ARROW_OPERATOR
pointer operator ->() const { return &(operator *()); }
#endif
self& operator ++() {
node = (link_type)((*node).next);
return *this ;
}
self operator ++(int ) {
self tmp = *this ;
++*this ;
return tmp;
}
self& operator --() {
node = (link_type)((*node).prev);
return *this ;
}
self operator --(int ) {
self tmp = *this ;
--*this ;
return tmp;
}
};
四、list 数据结构 list 是一个循环双向链表,并在首尾之间设置了一个哨兵位(Sentinel Node)。该空白节点用于统一处理区间逻辑,满足 STL'前闭后开'的要求。容器只需记录哨兵位地址即可操作整个链表。
template <class T , class Alloc = alloc>
class list {
protected :
typedef void * void_pointer;
typedef __list_node<T> list_node;
typedef list_node* link_type;
protected :
link_type node;
};
利用哨兵位,begin() 和 end() 的定义变得非常简洁:
template <class T , class Alloc = alloc>
class list {
public :
iterator begin () { return (link_type)((*node).next); }
iterator end () { return node; }
bool empty () const { return node->next == node; }
size_type size () const {
size_type result = 0 ;
distance (begin (), end (), result);
return result;
}
reference front () { return *begin (); }
reference back () { return *(--end ()); }
protected :
link_type node;
};
五、list 的构造和内存管理 list 默认使用 alloc 作为空间配置器,并定义了专门的 list_node_allocator 按节点大小分配内存。
template <class T , class Alloc = alloc>
class list {
protected :
typedef simple_alloc<list_node, Alloc> list_node_allocator;
link_type get_node () { return list_node_allocator::allocate (); }
void put_node (link_type p) { list_node_allocator::deallocate (p); }
};
template <class T , class Alloc >
class simple_alloc {
public :
static T* allocate (size_t n) {
return 0 == n ? 0 : (T*)Alloc::allocate (n * sizeof (T));
}
static T* allocate (void ) {
return (T*)Alloc::allocate (sizeof (T));
}
static void deallocate (T* p, size_t n) {
if (0 != n) Alloc::deallocate (p, n * sizeof (T));
}
static void deallocate (T* p) {
Alloc::deallocate (p, sizeof (T));
}
};
get_node 负责开辟节点,put_node 负责释放。创建节点时还需调用 construct 初始化数据,销毁时先析构再释放:
template <class T , class Alloc = alloc>
class list {
protected :
link_type create_node (const T& x) {
link_type p = get_node ();
__STL_TRY {
construct (&p->data, x);
} __STL_UNWIND(put_node (p));
return p;
}
void destroy_node (link_type p) {
destroy (&p->data);
put_node (p);
}
};
即使是空链表,内部也会初始化一个指向自己的哨兵节点。
六、list 的核心接口
6.1 insert 与 push_back/front 插入分为两步:开辟空间创建节点,然后调整指针将其挂入链表。
iterator insert (iterator position, const T& x) {
link_type tmp = create_node (x);
tmp->next = position.node;
tmp->prev = position.node->prev;
(link_type (position.node->prev))->next = tmp;
position.node->prev = tmp;
return tmp;
}
push_front 和 push_back 本质上都是调用 insert:
void push_front (const T& x) { insert (begin (), x); }
void push_back (const T& x) { insert (end (), x); }
6.2 erase 与 pop_back/front iterator erase (iterator position) {
link_type next_node = (link_type)(position.node->next);
link_type prev_node = (link_type)(position.node->prev);
prev_node->next = next_node;
next_node->prev = prev_node;
destroy_node (position.node);
return iterator (next_node);
}
pop_front 和 pop_back 同样依赖 erase:
void pop_front () { erase (begin ()); }
void pop_back () {
iterator tmp = end ();
erase (--tmp);
}
6.3 clear 与 remove 清除所有节点时,遍历链表逐一销毁,最后重置哨兵位:
template <class T , class Alloc >
void list<T, Alloc>::clear () {
link_type cur = (link_type)node->next;
while (cur != node) {
link_type tmp = cur;
cur = (link_type)cur->next;
destroy_node (tmp);
}
node->next = node;
node->prev = node;
}
template <class T , class Alloc >
void list<T, Alloc>::remove (const T& value) {
iterator first = begin ();
iterator last = end ();
while (first != last) {
iterator next = first;
++next;
if (*first == value)
erase (first);
else
first = next;
}
}
template <class T , class Alloc >
void list<T, Alloc>::unique () {
iterator first = begin ();
iterator last = end ();
if (first == last) return ;
iterator next = first;
while (++next != last) {
if (*first == *next)
erase (next);
else
first = next;
}
}
6.4 transfer 与 splice transfer 是底层迁移操作,将 [first, last) 区间移动到 position 之前。它涉及 6 个指针的修改,为 splice 等高级功能奠定基础。
protected :
void transfer (iterator position, iterator first, iterator last) {
if (position != last) {
(*(link_type ((*last.node).prev))).next = position.node;
(*(link_type ((*first.node).prev))).next = last.node;
(*(link_type ((*position.node).prev))).next = first.node;
link_type tmp = (link_type)((*position.node).prev);
(*position.node).prev = (*last.node).prev;
(*last.node).prev = (*first.node).prev;
(*first.node).prev = tmp;
}
}
对外公开的 splice 接口提供了三种重载,支持整表迁移、单节点迁移或区间迁移:
public :
void splice (iterator position, list& x) {
if (!x.empty ())
transfer (position, x.begin (), x.end ());
}
void splice (iterator position, list&, iterator i) {
iterator j = i;
++j;
if (position == i || position == j) return ;
transfer (position, i, j);
}
void splice (iterator position, list&, iterator first, iterator last) {
if (first != last)
transfer (position, first, last);
}
6.5 merge 与 sort merge 合并两个有序链表,保持结果有序。利用 transfer 将较小元素逐个迁移到目标链表前:
public :
template <class T , class Alloc >
void list<T, Alloc>::merge (list<T, Alloc>& x) {
iterator first1 = begin ();
iterator last1 = end ();
iterator first2 = x.begin ();
iterator last2 = x.end ();
while (first1 != last1 && first2 != last2) {
if (*first2 < *first1) {
iterator next = first2;
transfer (first1, first2, ++next);
first2 = next;
} else
++first1;
}
if (first2 != last2)
transfer (last1, first2, last2);
}
reverse 逆置链表,通过不断将后续节点迁移至头部实现:
public :
template <class T , class Alloc >
void list<T, Alloc>::reverse () {
if (node->next == node || (link_type)(node->next)->next == node)
return ;
iterator first = begin ();
++first;
while (first != end ()) {
iterator old = first;
++first;
transfer (begin (), old, first);
}
}
sort 排序算法较为特殊。由于 list 不支持随机访问迭代器,无法直接使用库提供的 std::sort。SGI STL 采用了自底向上的归并排序:
将单个节点视为已排序链表放入临时数组;
两两合并长度相同的有序链表;
重复直至所有节点归并为一个大链表。
public :
template <class T , class Alloc >
void list<T, Alloc>::sort () {
if (node->next == node || (link_type)(node->next)->next == node)
return ;
list<T, Alloc> carry;
list<T, Alloc> counter[64 ];
int fill = 0 ;
while (!empty ()) {
carry.splice (carry.begin (), *this , begin ());
int i = 0 ;
while (i < fill && !counter[i].empty ()) {
counter[i].merge (carry);
carry.swap (counter[i++]);
}
carry.swap (counter[i]);
if (i == fill)
++fill;
}
for (int i = 1 ; i < fill; ++i)
counter[i].merge (counter[i - 1 ]);
swap (counter[fill - 1 ]);
}
这种实现避免了递归分割,直接在链表上完成拼接,效率较高。
相关免费在线工具 加密/解密文本 使用加密算法(如AES、TripleDES、Rabbit或RC4)加密和解密文本明文。 在线工具,加密/解密文本在线工具,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
JSON 压缩 通过删除不必要的空白来缩小和压缩JSON。 在线工具,JSON 压缩在线工具,online