从零实现一个 STL 风格的双向链表(list)——设计、实现与细节剖析
本文深入剖析了STL中list容器的实现原理与设计思想。list采用带头节点的双向循环链表结构,具有O(1)时间复杂度的插入删除操作和稳定的迭代器特性。文章从节点结构、迭代器设计、内存管理等角度,详细讲解了list的核心实现机制,包括哨兵节点的作用、迭代器的封装技巧、RAII资源管理原则等。特别强调了STL list的工程实现细节,如边界条件处理、const正确性保证、异常安全设计等。通过对比ST
在 STL 容器中,list 是一个非常经典、同时又极具“工程味道”的容器。 与 vector 不同,它不依赖连续内存,而是通过节点 + 指针构成的双向链表来组织数据,这也使得它在插入、删除等操作上具备天然优势。
本文将围绕一份手写 STL 风格的 list 实现,从整体设计 → 关键数据结构 → 迭代器 → 接口实现 → 边界与安全性,完整拆解一个工业级 list 容器的实现逻辑。
一、整体设计思路:为什么 STL 的 list 要这样设计?
在正式看代码之前,先明确 STL list 的几个核心设计目标:
-
任意位置 O(1) 插入 / 删除
-
插入、删除不使迭代器失效
-
支持前向 / 后向遍历
-
接口行为与 STL 一致(begin / end / erase / insert)
-
异常安全 & 内存安全
要满足这些目标,最自然的数据结构选择就是:
带头结点的双向循环链表
二、节点结构:ListNode 的设计哲学
template<class T>
class ListNode
{
public:
T _data;
ListNode<T>* _prev;
ListNode<T>* _next;
};
1️⃣ 为什么是双向链表?
-
单向链表:
-
删除一个节点,需要知道前驱 → 不方便
-
-
双向链表:
-
每个节点都知道自己的前后 → 插入 / 删除 O(1)
-
STL 的 list 明确要求支持:
-
--it -
pop_back -
反向遍历
👉 单向链表无法自然支持这些操作。
2️⃣ 为什么一定要有 头结点(哨兵节点)?
你的代码采用的是:
_head->_next = _head;
_head->_prev = _head;
这是一个循环双向链表 + 哨兵节点的结构。
这样做的好处非常关键:
-
统一边界条件
-
空链表:
head->next == head -
非空链表:真实节点永远夹在中间
-
-
插入 / 删除时:
-
不需要区分“头插 / 尾插 / 中间插”
-
-
end()可以直接返回_head
这是 STL list 的核心工程技巧之一。
三、迭代器设计:ListIterator 不是指针的简单封装
template<class T>
struct ListIterator
{
Node* _node;
};
1️⃣ 为什么 STL 要“自己写迭代器”?
STL 的核心思想之一是:
算法与容器解耦
算法(如 for_each、find)不关心你底层是数组还是链表,只关心:
-
++it -
*it -
it != end()
因此,list 必须提供一个行为类似指针的对象 —— 这就是迭代器。
2️⃣ 解引用与箭头运算符
T& operator* () const { return _node->_data; }
T* operator->() const { return &_node->_data; }
这使得:
*it // 访问数据
it->field // 访问成员
在使用体验上与指针完全一致。
3️⃣ 前置 / 后置 ++ -- 的区别
Self& operator++(); // ++it
Self operator++(int); // it++
-
前置:效率更高(推荐)
-
后置:返回旧值,符合 C++ 语义
STL 要求两者都支持,你的实现是标准做法。
4️⃣ const_iterator 的意义
typedef ListIterator<const T> const_iterator;
这意味着:
-
const list<int>只能读取元素 -
防止误修改容器内容
这是 STL 非常重要的一点: 👉 const 容器 ≠ 容器里元素是 const
四、list 的核心成员变量
Node* _head;
size_t _size;
1️⃣ 为什么要自己维护 size?
链表理论上可以通过遍历统计长度,但:
-
每次
size()都遍历 → O(n) -
STL 要求
size()是 O(1)
所以必须额外维护 _size。
五、构造 / 析构 / 拷贝:RAII 思想的体现
1️⃣ 默认构造函数
list()
{
_head = new Node();
_head->_next = _head->_prev = _head;
}
构造即建立一个合法的空容器状态。
2️⃣ 拷贝构造函数
list(const list& x) : list()
{
for (const auto& val : x)
push_back(val);
}
这里非常值得注意:
-
不直接拷贝节点指针
-
而是逐元素插入
原因:
-
两个 list 必须完全独立
-
否则析构会 double free
3️⃣ 析构函数与 clear()
~list()
{
clear();
delete _head;
}
这是标准 RAII:
-
谁
new,谁delete -
析构时必须释放所有节点
六、insert:list 的灵魂操作
iterator insert(iterator pos, const T& val)
插入本质只有四步:
-
找到
pos指向的节点cur -
找到它的前驱
prev -
新节点接入链表
-
更新前后指针
prev <-> newnode <-> cur
这是 O(1) 的,完全不依赖链表长度。
多个元素插入(n 个)
void insert(iterator pos, size_t n, const T& val)
关键点:
-
不能在循环中直接用
n更新_size -
因为
n会被减到 0 -
必须用单独的计数变量
这正是工程代码中非常容易出 bug 的地方。
七、erase:为什么 STL 的 erase 要返回迭代器?
iterator erase(iterator pos)
删除后返回 下一个有效位置:
it = erase(it);
这样你就可以安全地在遍历中删除:
for (auto it = l.begin(); it != l.end(); )
{
if (*it == x)
it = l.erase(it);
else
++it;
}
如果 erase 不返回值,这种写法会非常别扭。
八、区间 erase 的实现思想
erase(first, last)
STL 约定:
-
删除
[first, last) -
返回
last
你的实现:
-
先断链
-
再统一 delete 节点
-
最后一次性返回
这是最优写法,避免了反复调整指针。
九、front / back / pop 系列的安全性设计
assert(!empty());
这是调试期防御性编程:
-
避免非法访问
-
在 Debug 阶段尽早暴露错误
STL 的 list 在 Release 下是 UB,你的实现更“教学友好”。
十、这份 list 已经具备什么 STL 水平?
✅ 支持双向迭代
✅ O(1) 插入 / 删除
✅ 迭代器稳定
✅ RAII 内存管理
✅ const 正确性
✅ 行为与 STL list 一致
十一、还能继续扩展什么?
如果你想继续进阶,可以加:
-
splice -
merge -
sort -
reverse -
allocator 支持
-
iterator traits
十二、整体代码实现
#pragma once
#include <iostream>
#include <assert.h>
namespace List
{
template<class T>
class ListNode
{
public:
T _data;
ListNode<T>* _prev;
ListNode<T>* _next;
ListNode(const T& data = T())
:_data(data), _prev(nullptr), _next(nullptr)
{
}
};
template<class T>
struct ListIterator
{
typedef ListNode<T> Node;
typedef ListIterator<T> Self;
Node* _node;
ListIterator(Node* node) : _node(node) {}
ListIterator() : _node(nullptr) {}
T& operator* () const { return _node->_data; }
T* operator->() const { return &_node->_data; }
Self& operator++()
{
_node = _node->_next;
return *this;
}
Self operator++(int)
{
Self tmp = *this;
++(*this);
return tmp;
}
Self& operator--()
{
_node = _node->_prev;
return *this;
}
Self operator--(int)
{
Self tmp = *this;
--(*this);
return tmp;
}
bool operator==(const Self& s) const { return s._node == _node; }
bool operator!=(const Self& s) const { return s._node != _node; }
};
template<class T>
class list
{
public:
typedef ListNode<T> Node;
typedef ListIterator<T> iterator;
typedef ListIterator<const T> const_iterator;
// 构造 / 析构
list() :_head(new Node()), _size(0)
{
_head->_next = _head->_prev = _head;
}
list(const list& x) : list() // 委托构造
{
for (const auto& val : x) push_back(val);
}
list& operator=(const list& x)
{
if (this != &x)
{
list tmp(x);
swap(tmp);
}
return *this;
}
~list()
{
clear();
delete _head;
_head = nullptr;
}
void clear()
{
Node* cur = _head->_next;
while (cur != _head)
{
Node* next = cur->_next;
delete cur;
cur = next;
}
_head->_next = _head->_prev = _head;
_size = 0;
}
void swap(list& x)
{
std::swap(_head, x._head);
std::swap(_size, x._size);
}
bool empty() const { return _size == 0; }
size_t size() const { return _size; }
// 迭代器
iterator begin() { return _head->_next; }
iterator end() { return _head; }
const_iterator begin() const { return _head->_next; }
const_iterator end() const { return _head; }
// 反向迭代器
iterator rbegin() { return _head->_prev; }
iterator rend() { return _head; }
// 插入
iterator insert(iterator pos, const T& val)
{
Node* cur = pos._node;
Node* prev = cur->_prev;
Node* newnode = new Node(val);
newnode->_prev = prev;
newnode->_next = cur;
prev->_next = newnode;
cur->_prev = newnode;
++_size;
return iterator(newnode);
}
void insert(iterator pos, size_t n, const T& val)
{
size_t count = 0;
Node* cur = pos._node->_prev;
Node* next = cur->_next;
while (n--)
{
Node* newnode = new Node(val);
cur->_next = newnode;
newnode->_prev = cur;
cur = newnode;
++count;
}
cur->_next = next;
next->_prev = cur;
_size += count;
}
// push / pop
void push_back(const T& val) { insert(end(), val); }
void push_front(const T& val) { insert(begin(), val); }
void pop_back() { if (!empty()) erase(--end()); }
void pop_front() { if (!empty()) erase(begin()); }
// front / back
T& front() { assert(!empty()); return _head->_next->_data; }
T& back() { assert(!empty()); return _head->_prev->_data; }
const T& front() const { assert(!empty()); return _head->_next->_data; }
const T& back() const { assert(!empty()); return _head->_prev->_data; }
// 删除
iterator erase(iterator pos)
{
assert(pos._node != _head);
Node* cur = pos._node;
Node* next = cur->_next;
Node* prev = cur->_prev;
prev->_next = next;
next->_prev = prev;
delete cur;
--_size;
return iterator(next);
}
iterator erase(iterator first, iterator last)
{
if (first == last) return last;
Node* first_node = first._node;
Node* last_node = last._node->_prev;
Node* before_first = first_node->_prev;
Node* after_last = last_node->_next;
before_first->_next = after_last;
after_last->_prev = before_first;
Node* cur = first_node;
while (cur != after_last)
{
Node* next_node = cur->_next;
delete cur;
cur = next_node;
--_size;
}
return iterator(after_last);
}
private:
Node* _head;
size_t _size = 0;
};
}
更多推荐



所有评论(0)