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,erasereturns 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 anode_typehandle. 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:
erasedestroys the object immediately;extractpreserves the object inside a node handle. - Deallocation:
erasereleases memory immediately;extractdefers memory deallocation to the node handle's lifecycle. - Efficiency:
erasefollowed byinsertrequires reallocating memory;extractfollowed byinsertperforms zero heap allocations. - Key Modification:
erasecannot be used to mutate keys in-place;extractallows non-const access to the node's key before re-insertion.