




splice 是节点移动而非复制,不调用构造/析构函数,指针仍有效;仅限同类型同分配器 list 间操作;三种重载分别移动整个容器、单个节点或范围;目标迭代器不失效,源中被移节点的迭代器仍有效但不可用于原容器操作。
splice 的核心行为是“剪切粘贴”:它把源 list 中的节点直接挪到目标 list 的指定位置,不调用任何元素的构造或析构函数。这意味着如果原 list 中有指针指向某个节点,splice 后这些指针仍然有效——因为节点内存地址没变,只是在链表中的前后关系变了。
常见误用是以为 splice 会深拷贝数据,结果发现源 list 被清空(或部分清空),而目标 list 多了节点,但对象本身没重建。这在管理资源(如文件句柄、动态分配内存)时尤其危险——若节点内含非 trivial 类型且依赖析构清理,而你误删了源容器却忘了析构,就可能泄漏。
std::list 之间操作,不能跨类型(比如 list 拼到 list)T,同一分配器)splice(pos, other, first, last)),first 和 last 必须来自 other,且 first 可达 last(last 可为 other.end())标准库提供三个主要重载,参数语义差异直接影响行为边界:
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) 等价于追加。
splice 是少数几个不使**目标容器**迭代器失效的标准容器操作之一。但要注意:源容器中被移动节点对应的迭代器,在移动后依然有效(仍指向原节点),只是不再属于原容器;而源容器中未被移动的其他迭代器,也全部保持有效——因为 list 的节点不重排,仅调整指针。
容易踩的坑:
++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 或预建索引。