我以前搬过 map 里的数据。
搬完以后。
性能掉了一截。
我当时还不信。
“就搬个节点,能有多贵?”
后来才明白。
贵的不是逻辑。
是分配。
是拷贝。
当年:erase + insert 能用,但它会重新分配
你有两个 std::map。
想把一个 key 的元素从 A 移到 B。
老写法通常是。
#include <map>
std::map<int, int> a;
std::map<int, int> b;
void move_one(int key) {
auto it = a.find(key);
if (it == a.end()) return;
b.insert(*it);
a.erase(it);
}
这段代码能跑。
但 insert(*it) 可能会分配。
也可能会拷贝/移动 value。
如果 value 很大。
你就会感觉到疼。
线上啪一下:我们在热路径里“移动会话”,结果 allocator 成了热点
比如你写个小服务。
有两个 map。
一个放“活跃会话”。
一个放“待清理会话”。
每次状态切换都要搬一次。
搬着搬着。
profiling 里全是 operator new。
你会很崩溃。
C++17:extract,把节点摘下来
node handle 的核心动作是。
extract。
auto nh = a.extract(key);
如果 key 不存在。
nh 是空的。
如果存在。
nh 里装的是“整颗节点”。
它带着 key。
带着 value。
也带着它原来的那块分配出来的内存。
再插回去:不拷贝地搬家
if (nh) {
b.insert(std::move(nh));
}
这里的感觉很像。
我把树上的一个节点摘下来。
换一棵树插进去。
它不是“复制一份再删原来的”。
merge:把整棵树能合的都合过去
如果你想把 a 里能插进 b 的都搬过去。
可以用 merge。
b.merge(a);
插成功的节点会从 a 消失。
冲突的(key 已存在)的会留在 a。
你不用自己写循环。
一个小技巧:extract 之后你甚至可以改 key
node handle 允许你改 key。
这听起来有点危险。
但在“重命名 key”这种场景很实用。
auto nh = a.extract(old_key);
nh.key() = new_key;
b.insert(std::move(nh));
这里你就完成了“改 key + 搬家”。
而且尽量少触发分配。
关键结论
node handle 解决的不是语法。
它解决的是:搬节点别再付一遍分配和拷贝的账。
小结:当你在 map/set 之间搬数据时,先想想 extract/merge
当年我们用 erase/insert。
能做。
但在高频场景会很贵。
C++17 给了 node handle。
你少一次分配。
就少一次抖动。
线上也会更稳一点。