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.
Loading...
Searching...
No Matches
jh::conc::flat_pool< Key, Value, Hash, Alloc > Class Template Referencefinal

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_pooloperator= (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_pooloperator= (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

Detailed Description

template<typename Key, typename Value = jh::typed::monostate, typename Hash = jh::hash<Key>, typename Alloc = std::allocator<detail::value_t<Key, Value>>>
requires ((requires(const Key &k) { { Hash{}(k) } -> std::convertible_to<size_t>; }) && (requires(const Key &a, const Key &b) { { a == b } -> std::convertible_to<bool>; }) && jh::concepts::is_contiguous_reallocable<Key> && (jh::typed::monostate_t<Value> || jh::concepts::is_contiguous_reallocable<Value>))
class jh::conc::flat_pool< Key, Value, Hash, Alloc >

Hash-ordered, contiguous resource interning pool.

Conceptual Model

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.

Key-Value Semantics

The pool may operate in two modes:

  • Set-like: When Value is jh::typed::monostate, only keys are stored.
  • Map-like: Otherwise, the pool stores (Key, Value) pairs, where the value is constructed only upon first insertion.

Construction and Deduplication

Acquisition follows a two-phase lookup strategy:

  1. Shared-lock lookup to detect an existing entry.
  2. Exclusive-lock recheck followed by insertion if absent.

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.

Lifetime Management

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.

Concurrency Guarantees

  • Lookup operations acquire only shared locks.
  • Insertion and release require exclusive access.
  • Reference counting is performed atomically.

Reallocation Safety

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.

Template Parameters
KeyKey type defining object identity.
ValueOptional associated value type (jh::typed::monostate for key-only).
HashHash functor used to compute full-width hash values (jh::hash for auto hash-derivation).
AllocAllocator type for contiguous storage.
Note
Unlike pointer_pool or its user-facing interface observe_pool, flat_pool(resource_pool) does not permit moves.
As indicated by their user-facing interface names, 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.

Member Typedef Documentation

◆ allocator_type

template<typename Key, typename Value = jh::typed::monostate, typename Hash = jh::hash<Key>, typename Alloc = std::allocator<detail::value_t<Key, Value>>>
using jh::conc::flat_pool< Key, Value, Hash, Alloc >::allocator_type
Initial value:
typename std::allocator_traits<Alloc>::template rebind_alloc<detail::value_t<Key, Value>>

Allocator rebound for value storage_ only.

Constructor & Destructor Documentation

◆ flat_pool() [1/3]

template<typename Key, typename Value = jh::typed::monostate, typename Hash = jh::hash<Key>, typename Alloc = std::allocator<detail::value_t<Key, Value>>>
jh::conc::flat_pool< Key, Value, Hash, Alloc >::flat_pool ( std::uint64_t reserve_size = MIN_RESERVED_SIZE)
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:

  • the contiguous value storage,
  • the reference count buffer,
  • the occupation bitmap,
  • the hash-ordered entry index.

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.

Parameters
reserve_sizeInitial number of slots to reserve.

◆ flat_pool() [2/3]

template<typename Key, typename Value = jh::typed::monostate, typename Hash = jh::hash<Key>, typename Alloc = std::allocator<detail::value_t<Key, Value>>>
jh::conc::flat_pool< Key, Value, Hash, Alloc >::flat_pool ( const allocator_type & alloc)
inlineexplicit

Constructs a flat_pool using a custom allocator.

Initializes an empty pool with allocator-aware contiguous storage and reserves the minimum required capacity.

Parameters
allocAllocator used for internal value storage.

◆ flat_pool() [3/3]

template<typename Key, typename Value = jh::typed::monostate, typename Hash = jh::hash<Key>, typename Alloc = std::allocator<detail::value_t<Key, Value>>>
jh::conc::flat_pool< Key, Value, Hash, Alloc >::flat_pool ( std::uint64_t reserve_size,
const allocator_type & alloc )
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:

  • the contiguous value storage vector,
  • the reference count buffer,
  • the occupation bitmap,
  • the hash-ordered entry index.

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.

Parameters
reserve_sizeInitial number of slots to reserve.
allocAllocator used for contiguous value storage.

Member Function Documentation

◆ acquire() [1/2]

template<typename Key, typename Value = jh::typed::monostate, typename Hash = jh::hash<Key>, typename Alloc = std::allocator<detail::value_t<Key, Value>>>
template<typename KArg>
requires (jh::typed::monostate_t<Value>)
ptr jh::conc::flat_pool< Key, Value, Hash, Alloc >::acquire ( KArg && key)
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.

Template Parameters
KArgA cv/ref-qualified form of Key.
Parameters
keyThe key identifying the object.
Returns
A reference-counted handle to the pooled key, or a null handle on failure.

◆ acquire() [2/2]

template<typename Key, typename Value = jh::typed::monostate, typename Hash = jh::hash<Key>, typename Alloc = std::allocator<detail::value_t<Key, Value>>>
template<typename KArg, typename... Args>
requires (!jh::typed::monostate_t<Value>)
ptr jh::conc::flat_pool< Key, Value, Hash, Alloc >::acquire ( KArg && key,
std::tuple< Args... > args_tuple )
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.

Construction Semantics
  • The key is used for lookup and deduplication.
  • By default, the value is constructed by forwarding the provided arguments to Value's constructor.
  • For 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.
  • Repeated calls with the same key but different argument tuples will always return the originally constructed value.
Custom Value Construction

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:

template<>
struct value_factory<Foo> {
template<typename... Args>
static Foo make(Args&&... args) {
// user-defined construction logic
}
};
}
Public customization and extension points for jh::conc containers.
Definition flat_pool.h:245

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.

Template Parameters
KArgA cv/ref-qualified form of Key.
ArgsTypes of arguments used for value construction.
Parameters
keyThe key identifying the entry.
args_tupleTuple of arguments forwarded to value construction.
Returns
A reference-counted handle to the pooled entry, or a null handle on failure.

◆ capacity()

template<typename Key, typename Value = jh::typed::monostate, typename Hash = jh::hash<Key>, typename Alloc = std::allocator<detail::value_t<Key, Value>>>
std::size_t jh::conc::flat_pool< Key, Value, Hash, Alloc >::capacity ( )
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().

Returns
The current storage capacity.

◆ empty()

template<typename Key, typename Value = jh::typed::monostate, typename Hash = jh::hash<Key>, typename Alloc = std::allocator<detail::value_t<Key, Value>>>
bool jh::conc::flat_pool< Key, Value, Hash, Alloc >::empty ( )
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.

Returns
true if the pool has no active entries; otherwise false.

◆ find()

template<typename Key, typename Value = jh::typed::monostate, typename Hash = jh::hash<Key>, typename Alloc = std::allocator<detail::value_t<Key, Value>>>
ptr jh::conc::flat_pool< Key, Value, Hash, Alloc >::find ( const Key & key)
inline

Looks up an existing pooled object without creating a new one.

Parameters
keyThe key to search for.
Returns
A valid handle if the key exists; otherwise, a null handle.
Note
Unlike acquire(), this function never inserts new entries.

◆ occupancy_rate()

template<typename Key, typename Value = jh::typed::monostate, typename Hash = jh::hash<Key>, typename Alloc = std::allocator<detail::value_t<Key, Value>>>
std::pair< std::size_t, std::size_t > jh::conc::flat_pool< Key, Value, Hash, Alloc >::occupancy_rate ( )
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.

Consistency Guarantee

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.

Hot-Path vs Cold-Path Interpretation

This metric is intended for observational and heuristic use. It does not imply that the pool should be immediately shrunk when utilization appears low.

  • Hot paths: Capacity growth reflects real demand. If the pool has expanded to a certain size, it indicates that the workload has required that capacity at some point. Shrinking on the hot path is therefore discouraged, as it may introduce allocation jitter and negate the benefit of prior expansion.
  • Cold paths: When the pool remains underutilized for an extended period and health metrics consistently indicate low occupancy, a controlled shrink (e.g. via resize_pool()) may be considered to release unused memory.
Slot Reuse Characteristics

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.

Returns
A pair consisting of:
  1. the current storage capacity
  2. the number of active entries

◆ resize_pool()

template<typename Key, typename Value = jh::typed::monostate, typename Hash = jh::hash<Key>, typename Alloc = std::allocator<detail::value_t<Key, Value>>>
void jh::conc::flat_pool< Key, Value, Hash, Alloc >::resize_pool ( )
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.

◆ size()

template<typename Key, typename Value = jh::typed::monostate, typename Hash = jh::hash<Key>, typename Alloc = std::allocator<detail::value_t<Key, Value>>>
std::size_t jh::conc::flat_pool< Key, Value, Hash, Alloc >::size ( )
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:

  • Released entries that are eligible for reuse are not counted.
  • Internal storage may contain inactive slots beyond this count.
Returns
The number of active entries.

The documentation for this class was generated from the following file: