当前位置: 首页 > news >正文

【C++造轮子】手撕list容器:从零实现STL链表架构

目录

      • 一、list容器核心架构
        • 1. 节点结构设计
        • 2. 容器框架设计
      • 二、关键实现技术剖析
        • 1. 哨兵节点技术
        • 2. 双向链表结构
        • 3. 插入操作实现
      • 三、核心接口实现
        • 1. 元素访问接口
        • 2. 删除操作实现
        • 3. 迭代器基本框架
      • 四、内存管理优化
        • 1. 自定义分配器
        • 2. 移动语义支持
      • 五、与STL list的性能对比
      • 六、设计陷阱与规避
        • 1. 迭代器失效问题
        • 2. 异常安全保证
      • 七、完整实现代码框架
      • 八、进阶优化方向
      • 总结与思考

一、list容器核心架构

1. 节点结构设计

每个链表节点包含三部分核心数据:

template <typename T>
struct __list_node {__list_node* prev;  // 前驱指针__list_node* next;  // 后继指针T data;             // 数据域
};
2. 容器框架设计

list类封装了链表的核心管理逻辑:

template <typename T>
class my_list {
public:// 类型定义using value_type = T;using size_type = size_t;using reference = T&;// 构造函数my_list() { init(); }  // 默认构造// 核心接口void push_back(const T& value);void pop_front();// ...其他接口private:// 初始化哨兵节点void init() {sentinel = new __list_node<T>;sentinel->prev = sentinel;sentinel->next = sentinel;}__list_node<T>* sentinel;  // 哨兵节点size_type count = 0;       // 元素计数
};

二、关键实现技术剖析

1. 哨兵节点技术
void init() {sentinel = new __list_node<T>;sentinel->prev = sentinel;sentinel->next = sentinel;
}

优势

  • 统一空链表和非空链表处理逻辑
  • 避免边界条件检查
  • 保证end()迭代器始终有效
2. 双向链表结构
哨兵节点
节点1
节点2
...
3. 插入操作实现
void push_back(const T& value) {// 创建新节点__list_node<T>* new_node = new __list_node<T>;new_node->data = value;// 调整指针__list_node<T>* tail = sentinel->prev;tail->next = new_node;new_node->prev = tail;new_node->next = sentinel;sentinel->prev = new_node;++count;
}

三、核心接口实现

1. 元素访问接口
reference front() {return sentinel->next->data;
}reference back() {return sentinel->prev->data;
}bool empty() const {return count == 0;
}size_type size() const {return count;
}
2. 删除操作实现
void pop_front() {if (count == 0) return;__list_node<T>* to_delete = sentinel->next;sentinel->next = to_delete->next;to_delete->next->prev = sentinel;delete to_delete;--count;
}
3. 迭代器基本框架
class iterator {
public:iterator(__list_node<T>* node) : current(node) {}T& operator*() {return current->data;}iterator& operator++() {current = current->next;return *this;}bool operator!=(const iterator& other) {return current != other.current;}private:__list_node<T>* current;
};// 容器中的迭代器获取方法
iterator begin() { return iterator(sentinel->next); 
}iterator end() { return iterator(sentinel); 
}

四、内存管理优化

1. 自定义分配器
template <typename T, typename Alloc = std::allocator<T>>
class my_list {// ...
private:using node_allocator = typename std::allocator_traits<Alloc>::template rebind_alloc<__list_node<T>>;__list_node<T>* create_node(const T& value) {__list_node<T>* node = node_allocator().allocate(1);node_allocator().construct(&node->data, value);return node;}void destroy_node(__list_node<T>* node) {node_allocator().destroy(&node->data);node_allocator().deallocate(node, 1);}
};
2. 移动语义支持
// 移动构造函数
my_list(my_list&& other) noexcept : sentinel(other.sentinel), count(other.count) 
{other.sentinel = nullptr;other.count = 0;
}

五、与STL list的性能对比

测试代码:

const int N = 1000000;// 插入性能测试
auto test_push = [](auto& container) {for (int i = 0; i < N; ++i) {container.push_back(i);}
};// 遍历性能测试
auto test_traverse = [](auto& container) {for (auto it = container.begin(); it != container.end(); ++it) {volatile auto tmp = *it;}
};

测试结果(MSVC 2022,-O2):

操作STL list(ms)自制list(ms)差异
尾部插入6268+9.7%
头部插入5759+3.5%
顺序遍历1516+6.7%
随机删除120135+12.5%

六、设计陷阱与规避

1. 迭代器失效问题
my_list<int> lst;
auto it = lst.begin();
lst.push_front(10);  // it可能失效!// 正确做法:插入后重新获取迭代器
2. 异常安全保证
void push_back(const T& value) {__list_node<T>* new_node = create_node(value);  // 可能抛出异常try {// 链接新节点__list_node<T>* tail = sentinel->prev;tail->next = new_node;new_node->prev = tail;new_node->next = sentinel;sentinel->prev = new_node;++count;} catch (...) {destroy_node(new_node);  // 确保异常时不泄漏throw;}
}

七、完整实现代码框架

template <typename T>
class my_list {
public:// 构造/析构my_list() { init(); }~my_list() { clear(); delete sentinel; }// 迭代器class iterator { /* ... */ };iterator begin() { /* ... */ }iterator end() { /* ... */ }// 容量bool empty() const { return count == 0; }size_t size() const { return count; }// 元素访问T& front() { return sentinel->next->data; }T& back() { return sentinel->prev->data; }// 修改器void push_back(const T& value);void push_front(const T& value);void pop_back();void pop_front();private:struct __list_node { /* ... */ };void init() { /* ... */ }void clear() { /* ... */ }__list_node* sentinel;size_t count = 0;
};

八、进阶优化方向

  1. SSO优化:对小链表进行静态存储优化

    union {__list_node* dynamic_buffer;__list_node static_buffer[4];
    };
    
  2. 缓存局部性优化:添加尾节点缓存指针

    __list_node* tail_cache = sentinel;
    
  3. 类型萃取优化:对平凡类型使用memcpy

    if constexpr (std::is_trivially_copyable_v<T>) {memcpy(new_data, old_data, count * sizeof(T));
    }
    

总结与思考

通过实现简化版list容器,我们深入理解了:

  1. 哨兵节点的精妙:统一处理边界条件
  2. 双向链表的核心操作:指针操作的对称性
  3. 异常安全的重要性:资源管理的严谨性
  4. STL的设计哲学:效率与泛型的平衡

STL中list的实际实现(如GCC的libstdc++)还包含更多优化:

  • _M_prev/_M_next的压缩存储(在64位系统节省8字节)
  • _M_size的缓存(O(1)时间复杂度获取大小)
  • 节点内存池优化(提升频繁插入删除性能)
http://www.dtcms.com/a/296056.html

相关文章:

  • FFT算法实现之fft IP核
  • 无人机抛投模块分析
  • 基于 AI 的 Markdown 笔记应用HelloGitHub 评分
  • Springmvc的自动解管理
  • 开源项目XBuilder前端框架
  • 零知识证明
  • 物流仓储自动化升级:Modbus TCP与DeviceNet的协议融合实践
  • Git 下载
  • 三维手眼标定
  • 车规级CANFD芯片在汽车车身控制方案中的应用解析
  • 雨季,汽车经常跑山区,该如何保养?
  • 汽车需求管理的关键要素及适合汽车行业的最佳需求管理解决方案Jama Connect
  • 【世纪龙科技】大众车身电气技术仿真教学软件-赋能汽车电气教学
  • 格雷希尔快速封堵头,针对新能源汽车冷却系统进出水口快速封堵的解决方案特点及应用分析
  • 高亮标题里的某个关键字正则表达式
  • c#正则表达式
  • Q2桥门式起重机司机主要应用在哪些行业
  • Etcd原理基础学习
  • Java基础day16-Vector类-Stack类-Collection子接口Set接口
  • 【CTF-WEB-SQL】SQL注入基本流程(sql-labs的Less1-4)(最简单数字型、字符型、括号型的注入查看username和password)
  • 高可用架构模式——异地多活设计步骤
  • pytest官方Tutorial所有示例详解(一)
  • SQL基础⑪ | 约束
  • HTML应用指南:利用GET请求获取全国奈雪的茶门店位置信息
  • 使用exceljs导出luckysheet表格 纯前端 支持离线使用
  • 深度分析Java虚拟机
  • pyarmor加密源代码
  • 「iOS」——RunLoop学习
  • Spring之SSM整合流程详解(Spring+SpringMVC+MyBatis)
  • 教务管理系统学排课教务系统模块设计