Extract vs Erase in Modern C++ Containers

In modern C++ (introduced in C++17), both erase and extract remove elements from associative containers like std::set, std::map, std::unordered_set, and std::unordered_map, but they handle object lifetimes and memory allocations fundamentally differently. While erase destroys the target element and deallocates its underlying node memory, extract decouples the node from the container's data structure without destroying the stored value or freeing memory, transferring ownership of the raw node to the caller.

Memory Management and Object Destruction

When you call erase on an associative container, the container locates the element, unlinks it from the internal data structure (such as a red-black tree or a hash table bucket), invokes the destructor of the stored element, and returns the node's memory to the allocator. Following an erase operation, the element ceases to exist.

In contrast, extract unlinks the element from the container and wraps the internal node in a move-only object known as a node handle (exposed via container::node_type). During this operation, neither the key nor the mapped value is destroyed, and the allocator is never invoked to deallocate memory. The caller becomes the owner of this node handle. If the node handle is allowed to go out of scope without being inserted elsewhere, its destructor will destroy the element and deallocate the node.

Return Values

The operations provide different return values suited to their purposes:

  • erase: Depending on the overload used, erase returns an iterator following the removed element, or an integral count of elements removed (0 or 1 for unique containers).
  • extract: Overloads accepting an iterator or a key return a node_type handle. If an iterator overload is used, the returned node handle contains the element. If a key overload is used and the key is not found, an empty node handle is returned.

In-Place Key Mutation

In associative containers, keys are immutable to preserve container invariants (e.g., ordering or hash placement). Prior to C++17, changing a key required copying or moving the value to a temporary object, erasing the old element, and inserting a new element, which caused unnecessary allocations and deallocations.

With extract, you can pull the node out of the container, modify the key directly via the node handle's .key() member function, and re-insert the node into the container:

auto node = my_map.extract("old_key");
if (!node.empty()) {
    node.key() = "new_key";
    my_map.insert(std::move(node));
}

This operation performs zero heap allocations or deallocations and avoids copying or moving the mapped payload.

Splicing Nodes Across Compatible Containers

Because a node handle retains ownership of the allocated memory, extract allows you to move nodes between two different containers without reallocating memory. As long as the two containers have compatible types (the same node, allocator, and key/value types, even if their comparison or hash predicates differ), an extracted node from one container can be inserted directly into another.

Summary of Differences

  • Destruction: erase destroys the object immediately; extract preserves the object inside a node handle.
  • Deallocation: erase releases memory immediately; extract defers memory deallocation to the node handle's lifecycle.
  • Efficiency: erase followed by insert requires reallocating memory; extract followed by insert performs zero heap allocations.
  • Key Modification: erase cannot be used to mutate keys in-place; extract allows non-const access to the node's key before re-insertion.