CodeRigor

FREE LESSON

hive storage: stable entities with the reference implementation

Practice stable insertion and erasure using pinned plf::hive, with a consistent id index and lifetime checks. Native std::hive remains unavailable here.

Continue after container contracts and hash lookup. The workload is a live entity store: other systems retain pointers while unrelated entities are added or removed. We need stability during churn without assuming contiguous storage or queue order.

Name the backend being exercised

Native std::hive is unavailable in the installed standard library. This lesson runs Matthew Bentley's pinned plf::hive 1.55.16 reference implementation, commit 1e127fd2d202f33523691b2310eedf96a1998978, under its zlib license. Include <plf_hive.h>; the grader and editor expose that exact header. No namespace alias pretends it is std::hive, and the native feature probe remains unavailable. The common storage and lifetime ideas transfer, but passing this practice is not evidence of native-library availability or full conformance.

Blocks preserve the addresses of unrelated live elements
Insertion can use a hole or a new block. Erasing one entity does not compact the surviving entities into new addresses.
Blocks preserve the addresses of unrelated live elements
Insertion can use a hole or a new block. Erasing one entity does not compact the surviving entities into new addresses.

The C++26 hive design groups elements into blocks and tracks erased slots. Insertion position is unspecified so space can be reused. Existing non-erased element iterators and references survive ordinary insert/erase operations, but an erased element's handles are invalid. Other operations have their own invalidation rules: do not generalize this promise to sort, assignment, capacity-changing operations, replacement or a cached past-the-end iterator.

An empty element block may be retired or reserved for reuse; do not promise that every empty block is retained. Exact block sizes, growth and address-reuse policies are implementation details. Iterators are bidirectional, not random access. Insertion/erasure are efficient for this workload, but allocation and locality tradeoffs still require measurements on the intended data.

A stable pointer does not identify a future replacement object

An erased slot can hold a different entity later
Only B survives continuously. The old A pointer is invalid after erasure, regardless of whether C later occupies the same address.
An erased slot can hold a different entity later
Only B survives continuously. The old A pointer is invalid after erasure, regardless of whether C later occupies the same address.

Do not dereference a pointer after its entity is erased. Slot reuse can create an address-equality trap: the address may be numerically the same while the lifetime/identity contract changed. Use stable ids and reacquire a pointer after lookup; use generations when an application must distinguish reused handles. This exercise allows id reuse only after deletion and returns live pointers; it does not provide a stale-handle detection API.

C++
#include <plf_hive.h>
#include <cstdio>
int main() {
    plf::hive<int> values;auto first=values.insert(7);int* retained=&*first;
    for(int i=0;i<2000;++i)values.insert(i);
    auto other=values.insert(99);values.erase(other);
    std::printf("plf::hive reference: stable %d, value %d\n",&*first==retained,*retained);
}

This program uses reference iterators deliberately. It does not print exact block counts, address order or insertion order. Those are not the contract.

Keep the id index consistent with storage

An unordered_map can map entity ids to live hive iterators. Rehashing the index changes its own iterators, not the entity locations stored in the hive. Every erase must retire the corresponding id entry. Duplicates must be rejected before creating a second entity with the same key.

Two data structures represent one set of live entities
Insert commits both structures or rolls back the new entity on index-allocation failure. Erase retires both representations.
Two data structures represent one set of live entities
Insert commits both structures or rolls back the new entity on index-allocation failure. Erase retires both representations.

During filtering, save the id before destroying the entity and continue from the iterator returned by erase. Incrementing an erased iterator is invalid. Return removed ids in sorted order if the public API requires deterministic output; do not equate hive iteration order with chronology.

A default copy of a store containing iterators would copy references into the original store. The build keeps EntityStore noncopyable. A production copy operation would need to build a fresh index into the copied storage, and move operations would need their own allocator and iterator analysis. Those are separate contracts, not incidental defaults.

The graded exercise checks growth, unrelated erasure, consecutive filtered removals, id reuse, pointer identity, empty/refilled storage and agreement with a map model. Its actual pinned library runs under ASan/UBSan/LSan. The standard hive API can be evaluated separately when the installed library supplies it; this backend remains explicitly named and reproducible.

Watch & read

Free lectures, talks and readings that cover this skill from another angle. All resources →

Start with the contiguous-storage baseline and its tradeoffs; use this lesson and the proposal for the distinct stable-address hive design.