splice修改的是被转移节点的next/prev指针,使其从源list脱离并挂入目标list指定位置;节点内存地址、内容及析构时机均不变,不调用拷贝或移动构造。

splice 函数到底改的是谁的指针
splice 不复制节点,也不构造新对象,它直接把源 list 中的节点“摘下来”,再“挂到”目标 list 的指定位置。关键点在于:所有被转移节点的 next/prev 指针都会被重连,但节点本身的内存地址、内容、析构时机完全不变。
常见误解是以为它会调用拷贝构造或移动构造——其实不会。这也是 splice 高效(O(1) 或 O(n))的根本原因。
- 单节点转移:
dst.splice(dst_iter, src, src_iter)—— 把src中src_iter指向的**单个节点**插入到dst的dst_iter之前 - 区间转移:
dst.splice(dst_iter, src, first, last)—— 把src中[first, last)范围内的节点整体搬过去(last不参与转移) - 整表转移:
dst.splice(dst_iter, src)—— 把整个src搬到dst的dst_iter之前;执行后src变为空容器
为什么 splice 后原 list 迭代器可能失效
只有被转移走的那些节点对应的迭代器,在源 list 中彻底“作废”——不是悬垂,而是逻辑上已不属于该容器。而没被移动的迭代器(如果有的话)仍有效。但注意:src 在整表 splice 后变为空,此时 src.begin() == src.end(),任何曾指向它的迭代器都不能再解引用或递增。
典型翻车场景:
立即学习“C++免费学习笔记(深入)”;
- 在循环中边遍历边
splice某些节点,却继续用原++it—— 若it刚好被移走,++it行为未定义 - 转移后还试图对源
list调用size()或遍历,却忘了它可能已被清空 - 误以为
splice会保留源容器中剩余节点的相对顺序——它确实保留,但前提是这些节点没被移走
splice 和 move 语义混用的坑
std::list::splice 本身不涉及 move,但它常和 std::move 搞混。比如有人写 dst.splice(it, std::move(src)),这毫无意义:因为 splice 参数是左值引用(list&),传入右值会触发移动构造生成临时对象,再立即被 splice 搬空,而原 src 本身毫发无损。
真正需要 move 的地方其实是容器赋值或返回:
- 错误:
dst.splice(it, std::move(src))—— 编译通过但逻辑错,src没变 - 正确:
dst.splice(it, src)即可;若想清空src并避免后续误用,手动调src.clear()或直接让它生命周期结束 - 若要“接管”整个 list 内容且让原对象不可用,用移动赋值:
dst = std::move(src),但这会销毁原节点并重建,不是splice的零开销行为
跨 list 转移时的类型与分配器约束
splice 要求两个 list 必须是同一类型(T 相同)、使用相同的分配器(Allocator)。否则编译失败,错误信息类似:no matching member function for call to 'splice' 或更底层的 allocator_traits 不匹配。
尤其注意自定义分配器场景:
- 默认
std::allocator通常兼容,但若一个用MyAlloc,另一个用MyAlloc(模板参数不同),即使逻辑相同,也算不同类型 - 节点元素类型必须严格一致,不能靠隐式转换——
list无法splice到list,哪怕数值可转 - 如果你看到
static_assert失败提示 “allocators must compare equal”,说明分配器实例不满足a1 == a2,需检查是否传了不同实例或用了 stateful 分配器
最易忽略的一点:splice 后,被转移节点的 this 指针不变,但其所属容器变了——这意味着如果你在节点里存了指向容器的裸指针或引用,它不会自动更新,得手动维护。









