|
JH-Toolkit v1.4.1
An engineering-oriented C++20 toolkit with duck-typed concepts, static design, async coroutines, and semantic containers — header-only, RTTI-free, and concurrency-friendly.
|
Hash-ordered, contiguous resource interning pool. More...
#include <jh/concurrent/flat_pool.h>
Classes | |
| struct | ptr |
| Reference-counted handle to a pooled object. More... | |
Public Types | |
| using | value_type = detail::value_t<Key, Value> |
Stored element type (Key or std::pair<Key, Value>). | |
| using | allocator_type |
Allocator rebound for value storage_ only. | |
Public Member Functions | |
| flat_pool (std::uint64_t reserve_size=MIN_RESERVED_SIZE) | |
| Constructs a flat_pool with pre-reserved contiguous storage. | |
| flat_pool (const allocator_type &alloc) | |
| Constructs a flat_pool using a custom allocator. | |
| flat_pool (std::uint64_t reserve_size, const allocator_type &alloc) | |
| Constructs a flat_pool with a custom allocator and explicit reserved capacity. | |
| flat_pool (const flat_pool &other)=delete | |
| Copy construction is disabled to preserve handle and index validity. | |
| flat_pool & | operator= (const flat_pool &other)=delete |
| Copy assignment is disabled to prevent duplication of owned storage. | |
| flat_pool (flat_pool &&other)=delete | |
| Move construction is disabled because pooled objects are index-bound. | |
| flat_pool (flat_pool &&other, const allocator_type &alloc)=delete | |
| Allocator-aware move construction is disabled for safety. | |
| flat_pool & | operator= (flat_pool &&other)=delete |
| Move assignment is disabled to avoid dangling pool handles. | |
| template<typename KArg> requires (jh::typed::monostate_t<Value>) | |
| ptr | acquire (KArg &&key) |
| Retrieves or creates a pooled object associated with a key (set-like). | |
|
template<typename KArg> requires (!jh::typed::monostate_t<Value>) | |
| ptr | acquire (KArg &&key)=delete |
| Deleted overload for map-like pools without value arguments. | |
| template<typename KArg, typename... Args> requires (!jh::typed::monostate_t<Value>) | |
| ptr | acquire (KArg &&key, std::tuple< Args... > args_tuple) |
| Retrieves or creates a pooled key-value entry (map-like). | |
|
template<typename KArg, typename... Args> requires (jh::typed::monostate_t<Value>) | |
| ptr | acquire (KArg &&key, std::tuple< Args... > args_tuple)=delete |
| args acquire is deleted for set-like pools | |
| ptr | find (const Key &key) |
| Looks up an existing pooled object without creating a new one. | |
| bool | empty () |
| Checks whether the pool contains no active entries. | |
| std::size_t | capacity () |
| Returns the current storage capacity of the pool. | |
| std::size_t | size () |
| Returns the number of active entries in the pool. | |
| std::pair< std::size_t, std::size_t > | occupancy_rate () |
| Returns a snapshot of pool capacity and active entry count. | |
| void | resize_pool () |
| Shrinks internal storage to fit active entries. | |
Static Public Attributes | |
| static std::uint64_t constexpr | MIN_RESERVED_SIZE = 16 |
| Minimum reserved size for the pool. | |
Friends | |
| struct | flat_pool::ptr |
Hash-ordered, contiguous resource interning pool.
flat_pool interns objects by mapping keys to stable integer indices inside a contiguous storage vector. Each unique key corresponds to at most one active slot at any time.
The pool maintains a sorted index of (hash, index) pairs, allowing logarithmic lookup by hash followed by linear resolution of hash collisions. This design preserves the full entropy of the hash value and avoids bucket-based aliasing.
The pool may operate in two modes:
Value is jh::typed::monostate, only keys are stored. (Key, Value) pairs, where the value is constructed only upon first insertion. Acquisition follows a two-phase lookup strategy:
For map-like pools, value construction is deferred until the key is confirmed to be absent, ensuring that repeated acquisitions do not incur unnecessary construction cost.
Each slot maintains an atomic reference count. When the count reaches zero, the slot is marked as free and may be reused by subsequent insertions.
Slots are not immediately destroyed or removed from storage. Instead, they participate in a free-slot reuse mechanism that minimizes memory churn.
Although indices remain stable, vector reallocation may invalidate references or pointers to stored objects. To address this, the pool provides a no_reallocate_guard mechanism that prevents reallocation while dereferencing pooled objects in concurrent environments.
| Key | Key type defining object identity. |
| Value | Optional associated value type (jh::typed::monostate for key-only). |
| Hash | Hash functor used to compute full-width hash values (jh::hash for auto hash-derivation). |
| Alloc | Allocator type for contiguous storage. |
pointer_pool or its user-facing interface observe_pool, flat_pool(resource_pool) does not permit moves. pointer_pool(observe_pool) does not hold objects: it merely observes them. The worst-case scenario after a move is deduplication failure, but the system remains operational. flat_pool(resource_pool), however, fully owns objects. Once moves are permitted, the flat_pool::ptr handle becomes dangling, hence the move semantics are disabled to ensure safety. | using jh::conc::flat_pool< Key, Value, Hash, Alloc >::allocator_type |
Allocator rebound for value storage_ only.
|
inlineexplicit |
Constructs a flat_pool with pre-reserved contiguous storage.
Initializes an empty pool and pre-reserves internal storage to reduce reallocation overhead during early insertions.
The reservation applies to:
If reserve_size is smaller than MIN_RESERVED_SIZE, the minimum value is used instead. This guarantees a baseline capacity suitable for typical workloads and avoids pathological reallocation behavior.
No objects are constructed during initialization. All slots are created lazily upon first acquisition.
| reserve_size | Initial number of slots to reserve. |
|
inlineexplicit |
Constructs a flat_pool using a custom allocator.
Initializes an empty pool with allocator-aware contiguous storage and reserves the minimum required capacity.
| alloc | Allocator used for internal value storage. |
|
inlineexplicit |
Constructs a flat_pool with a custom allocator and explicit reserved capacity.
Initializes an empty pool using the provided allocator for contiguous value storage and pre-reserves internal capacity according to reserve_size.
The reservation applies uniformly to all internal structures, including:
If reserve_size is smaller than MIN_RESERVED_SIZE, the minimum value is used instead. This ensures a baseline capacity and avoids early reallocation under light workloads.
No objects are constructed during initialization. All slots are created lazily upon first successful acquisition.
| reserve_size | Initial number of slots to reserve. |
| alloc | Allocator used for contiguous value storage. |
|
inline |
Retrieves or creates a pooled object associated with a key (set-like).
This overload is available only when the pool operates in set-like mode (Value == jh::typed::monostate).
If an equivalent key already exists in the pool, a handle to the existing slot is returned. Otherwise, a new slot containing the key is created.
No value construction is involved in this mode.
| KArg | A cv/ref-qualified form of Key. |
| key | The key identifying the object. |
|
inline |
Retrieves or creates a pooled key-value entry (map-like).
This overload is available only when the pool operates in map-like mode (Value != jh::typed::monostate).
The value construction arguments are provided as a std::tuple and are used only if the key does not already exist in the pool. If an equivalent key is found, the existing entry is reused and the provided arguments are ignored.
This design ensures that expensive value construction is performed exactly once for each unique key, even under concurrent acquisition.
Value's constructor. std::shared_ptr<T> and std::unique_ptr<T>, the default behavior forwards the arguments to std::make_shared<T> and std::make_unique<T> respectively. Value construction is customizable through a public extension point: jh::conc::extension::value_factory<Value>. Users may specialize this template to override how values are created.
A custom factory may be provided as follows:
This mechanism is an intentional public injection point and allows customization without modifying or subclassing flat_pool.
Users should treat the argument tuple as initialization parameters, not update parameters.
| KArg | A cv/ref-qualified form of Key. |
| Args | Types of arguments used for value construction. |
| key | The key identifying the entry. |
| args_tuple | Tuple of arguments forwarded to value construction. |
|
inline |
Returns the current storage capacity of the pool.
This value represents the number of slots currently allocated in the underlying contiguous storage. It reflects historical peak demand rather than current usage.
A larger capacity does not imply high active usage. The pool may have grown due to a temporary workload spike and later released most entries without shrinking.
Capacity is adjusted only through explicit maintenance operations such as resize_pool().
|
inline |
Checks whether the pool contains no active entries.
Returns true if there are currently no live entries registered in the pool.
This function reflects the logical emptiness of the pool, not its physical storage state. Internal capacity and previously allocated slots may still exist even when the pool is empty.
true if the pool has no active entries; otherwise false.
|
inline |
Looks up an existing pooled object without creating a new one.
| key | The key to search for. |
acquire(), this function never inserts new entries.
|
inline |
Returns a snapshot of pool capacity and active entry count.
This function acts as a health observer rather than a strict capacity-management API. It reports the current storage capacity and the number of active entries as a logically consistent pair.
The returned (capacity, size) values are obtained under a single shared lock and therefore always reflect the same internal version of the pool state. Callers may rely on the two values being mutually consistent and not derived from different update epochs.
This metric is intended for observational and heuristic use. It does not imply that the pool should be immediately shrunk when utilization appears low.
resize_pool()) may be considered to release unused memory. The pool preferentially reuses the lowest-index free slots. As a result, after a temporary surge in capacity, newly inserted entries naturally migrate toward the front of the storage vector over time.
This behavior means that tail regions tend to become empty first during cooling phases, making them suitable candidates for release without disrupting active entries.
|
inline |
Shrinks internal storage to fit active entries.
This function scans for the highest-index active slot and reduces the capacity of internal storage to the smallest power-of-two sufficient to hold all active entries, subject to a minimum reserved size.
This operation acquires exclusive locks and must not be performed concurrently with active dereferencing unless guarded.
|
inline |
Returns the number of active entries in the pool.
This function reports the number of currently live, deduplicated entries registered in the pool.
The returned value corresponds to the number of keys present in the internal index, not the number of allocated slots.
In particular: