splice 是节点移动而非复制,不调用构造/析构函数,指针仍有效;仅限同类型同分配器 list 间操作;三种重载分别移动整个容器、单个节点或范围;目标迭代器不失效,源中被移节点的迭代器仍有效但不可用于原容器操作。

splice 会移动节点,不是复制
splice 的核心行为是“剪切粘贴”:它把源 list 中的节点直接挪到目标 list 的指定位置,不调用任何元素的构造或析构函数。这意味着如果原 list 中有指针指向某个节点,splice 后这些指针仍然有效——因为节点内存地址没变,只是在链表中的前后关系变了。
常见误用是以为 splice 会深拷贝数据,结果发现源 list 被清空(或部分清空),而目标 list 多了节点,但对象本身没重建。这在管理资源(如文件句柄、动态分配内存)时尤其危险——若节点内含非 trivial 类型且依赖析构清理,而你误删了源容器却忘了析构,就可能泄漏。
- 只适用于同类型
std::list之间操作,不能跨类型(比如list拼到list) - 被 splice 的节点所属的 list 必须和当前 list 是同一个模板实例(同一
T,同一分配器) - 若使用带迭代器范围的重载(如
splice(pos, other, first, last)),first和last必须来自other,且first可达last(last可为other.end())
三种 splice 调用方式的区别
标准库提供三个主要重载,参数语义差异直接影响行为边界:
-
splice(pos, other):把整个other搬到pos前面;other变为空 -
splice(pos, other, it):把other中迭代器it指向的单个节点移到pos前面;it必须有效且不能等于other.end() -
splice(pos, other, first, last):把[first, last)范围内的节点(左闭右开)整体移入;first和last都必须属于other,且first != last才真正移动(否则无操作)
注意:pos 是目标容器的插入位置,始终解释为“在 pos 之前插入”。例如 lst.splice(lst.begin(), other) 表示插到最前面;lst.splice(lst.end(), other) 等价于追加。
立即学习“C++免费学习笔记(深入)”;
迭代器失效规则:只有被移动的迭代器还有效
splice 是少数几个不使**目标容器**迭代器失效的标准容器操作之一。但要注意:源容器中被移动节点对应的迭代器,在移动后依然有效(仍指向原节点),只是不再属于原容器;而源容器中未被移动的其他迭代器,也全部保持有效——因为 list 的节点不重排,仅调整指针。
容易踩的坑:
- 对已 splice 出去的迭代器继续调用
++it或解引用,没问题;但它再也不能用于原other容器的 erase/insert/splice 等操作 - 若在
splice后还用other.begin()等获取新首节点,要小心——other可能已为空,other.begin() == other.end() - 不要在
splice过程中修改参与操作的任意迭代器变量,比如:auto it = other.begin(); splice(pos, other, it); ++it;—— 此时it已不属于other,但自增行为未定义(虽常能跑通,属未定义行为)
实际拼接场景:避免重复遍历
比如要把两个有序 list 合并成一个有序链表,别先 merge 再 splice,而应直接用 list::merge——它内部就是基于 splice 实现的高效归并。但如果你只是想把一截中间段“抽出来”插到另一处,splice 就是唯一低开销方案。
示例:从 src 中取出第 2 到第 4 个节点(0-indexed),插入到 dst 开头:
auto it1 = std::next(src.begin(), 1); // 第2个 auto it2 = std::next(src.begin(), 4); // 第5个(作为 last) dst.splice(dst.begin(), src, it1, it2);
这里没用 std::advance 是因为 list 迭代器是双向的,std::next 更安全;it2 必须是“末尾之后”的位置,否则范围不合法。
真正复杂的地方在于:当你要 splice 的范围依赖运行时条件(比如按值筛选),就必须先遍历找边界,而 list 不支持 O(1) 随机访问——这时候 splice 的优势会被抵消,不如考虑换用 vector 或预建索引。









