splice函数只重连节点指针,不拷贝也不析构元素,源list中元素内存地址不变,仅改变所属链表;时间复杂度o(1),零分配、零构造开销。

splice函数到底移动什么,不拷贝也不析构
std::list::splice 不复制元素,也不调用任何构造或析构函数——它只重连节点指针。这意味着源 list 中的元素对象内存地址不变,只是从一个链表“剪切”到另一个链表里。这点和 std::vector 的 insert 或 assign 有本质区别:后者必然触发拷贝/移动,而 splice 是 O(1) 时间、零分配、零构造开销的操作。
常见错误是误以为它会深拷贝数据,结果发现源 list 变空了,但目标 list 里对象的地址和原来一模一样——这正是预期行为,不是 bug。
三种重载怎么选:单个元素、区间、整个 list
splice 有三个常用重载,区别全在第二个参数(迭代器位置)和后续参数组合:
-
splice(pos, other):把other整个 list 搬到pos前面;other变为空 -
splice(pos, other, it):把other中单个节点(由it指向)搬到pos前面;it必须有效且属于other -
splice(pos, other, first, last):把[first, last)半开区间内的所有节点搬到pos前面;first和last都必须属于other,且first可达last
注意:pos 总是目标 list 的迭代器,且可以是 end();other 必须是**不同对象**(不能是 this),否则行为未定义;first/last 区间不能包含 pos(哪怕跨 list 也不行)。
最容易踩的坑:迭代器失效与跨容器误用
splice 后,只有被移动节点的源迭代器失效(比如 it 在 splice(pos, other, it) 后不能再用),但目标 list 的 pos 和其余迭代器全部保持有效——这是 std::list 的关键优势。
但以下情况会直接导致未定义行为:
- 对同一个 list 调用
splice(比如lst.splice(lst.begin(), lst, ++lst.begin())) - 用已失效的
it(如 splice 后又拿它当参数) - 把
first和last搞反,或传入不属于other的迭代器(编译不报错,运行崩溃)
示例安全写法:
std::list<int> a = {1,2,3}, b = {4,5,6};
auto it = b.begin();
a.splice(a.end(), b, it); // 把 b 的第一个元素(4)移到 a 尾部
// 此时 a == {1,2,3,4},b == {5,6},it 失效,不能再解引用</int>
为什么不用 assign 或 insert?性能和语义差异在哪
如果用 assign 或 insert 模拟类似效果,会触发每个元素的拷贝构造(或移动构造),对于非 trivial 类型(比如含文件句柄、锁、大缓冲区的对象),代价远高于指针重连;更严重的是,可能违反资源独占语义(比如移动后原对象应置为无效状态,但 splice 根本不碰对象本身)。
典型适用场景:
- 实现 LRU cache:把刚访问的节点快速提到 front,用
splice(begin(), l, it) - 归并两个已排序 list:用
splice把小元素节点逐个插入,避免重复构造 - 批量转移子任务队列:把待处理节点从 pending list “摘出”到 running list,零开销
真正难的不是调用语法,而是想清楚:你操作的是节点链接关系,不是对象生命周期。一旦混淆这两层,问题就藏在运行时里。
C++免费学习笔记(深入):立即使用
在学习笔记中,你将探索 C++ 的入门与实战技巧!











