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::pointer_pool< T, Hash, Eq > Class Template Referencefinal

Weak pointer-observed pool for immutable or structurally immutable objects. More...

#include <jh/concurrent/pointer_pool.h>

Public Member Functions

 pointer_pool (std::uint64_t reserve_size=MIN_RESERVED_SIZE)
 Constructs a pool with an initial reserved capacity.
 pointer_pool (const pointer_pool &)=delete
 Deleted copy constructor.
pointer_pooloperator= (const pointer_pool &)=delete
 Deleted copy assignment operator.
 pointer_pool (pointer_pool &&other) noexcept
 Move constructor.
pointer_pooloperator= (pointer_pool &&other) noexcept
 Move assignment operator.
template<typename... Args>
std::shared_ptr< T > acquire (Args &&... args) const =delete
 Deleted acquire() for const pointer_pool.
template<typename... Args>
std::shared_ptr< T > acquire (Args &&... args)
 Retrieves an object from the pool, or creates a new one if none exists.
void cleanup ()
 Removes expired weak references from the pool.
void cleanup_shrink ()
 Removes expired entries and conditionally shrinks the reserved capacity.
std::uint64_t size () const
 Gets the current number of elements in the pool.
std::uint64_t capacity () const
 Gets the current reserved size of the pool.
void clear ()
 Clears all entries and resets the pool to its initial state.

Static Public Attributes

static std::uint64_t constexpr MIN_RESERVED_SIZE = 16
 The minimum reserved capacity for the pool.

Detailed Description

template<typename T, typename Hash, typename Eq>
requires ( requires(const std::weak_ptr<T>& t) { { Hash{}(t) } -> std::convertible_to<size_t>; } && requires(const std::weak_ptr<T>& a, const std::weak_ptr<T>& b) { { Eq{}(a, b) } -> std::convertible_to<bool>; })
class jh::conc::pointer_pool< T, Hash, Eq >

Weak pointer-observed pool for immutable or structurally immutable objects.

Core Behavior

  1. Objects are always constructed first using the forwarded arguments.
  2. The pool lock is acquired only when attempting insertion.
  3. If an equivalent object already exists, that instance is returned and the temporary is discarded.
  4. If no match exists, the temporary becomes the canonical instance stored in the pool.

This construct-first, lock-then-insert pattern is required because lookup is defined by the object's own equality; no external key or pre-hash structure can be used.

Design Characteristics

  • Non-intrusive: The pool never owns objects; it records only std::weak_ptr while ownership remains external.
  • Deferred cleanup: Expired entries are removed only during insertion, capacity checks, or explicit cleanup calls.
  • Adaptive capacity: The container may grow or shrink depending on occupancy thresholds evaluated during insertion.
  • Thread-safe: Lookups and insertions coordinate through std::shared_mutex.
  • Discard-friendly: Temporary objects are cheap to abandon when a matching instance exists.

Usage Notes

  • Best suited for immutable or structurally immutable types whose identity is fully determined at construction.
  • For heavier objects, prefer a two-phase initialization pattern: construct only identity fields immediately and defer expensive setup until the object becomes the accepted instance.
  • Fields contributing to equality and hashing must remain constant while managed by the pool.

Concurrency and Safety

  • Concurrent calls to acquire() are safe.
  • Insertion and deduplication are atomic under exclusive locking.
  • Externally held std::shared_ptr objects remain valid even if the pool is cleared or destroyed.
Note
Hash and equality functors need only reflect object-level identity. When using jh::observe_pool, these are automatically derived from std::hash<T>() or adl hash(t) or t.hash(), and operator==() to ensure consistent behavior.
Warning
On Windows platforms (MinGW-w64 / MinGW-clang with UCRT or MSVCRT), additional std::atomic_thread_fence(std::memory_order_seq_cst) barriers are inserted to strengthen ordering at the language level. This eliminates ISO-level UB risks and preserves correctness within the C++ abstract machine.
However, certain Windows runtime and system-level synchronization implementations (e.g. SRWLock-backed std::shared_mutex) do not necessarily provide POSIX-equivalent global ordering behavior. Under extreme multi-core contention, rare visibility or reordering phenomena may still be observed.
These effects are platform characteristics rather than violations of the C++ standard and do not indicate undefined behavior in the pool implementations. pointer_pool may expose such behavior more readily due to heavier synchronization and hash-table interaction, but the underlying limitation applies to all concurrent pools in this module.
Windows builds are therefore considered compatible but not validated for extreme high-contention workloads. POSIX platforms remain the primary supported and reference environments.

Constructor & Destructor Documentation

◆ pointer_pool() [1/3]

template<typename T, typename Hash, typename Eq>
jh::conc::pointer_pool< T, Hash, Eq >::pointer_pool ( std::uint64_t reserve_size = MIN_RESERVED_SIZE)
inlineexplicit

Constructs a pool with an initial reserved capacity.

Parameters
reserve_sizeThe initial capacity to reserve for the internal hash set. Defaults to MIN_RESERVED_SIZE (16).

Initializes the pool's internal storage and establishes the adaptive resizing baseline. This constructor performs no object construction; it only reserves memory for the underlying std::unordered_set that stores weak references.

Note
The reserved size determines the initial hash set capacity and defines the minimum capacity threshold for future adaptive resizing. The pool will never shrink below MIN_RESERVED_SIZE (16), ensuring predictable allocation behavior and avoiding frequent reallocation during low-load periods.

◆ pointer_pool() [2/3]

template<typename T, typename Hash, typename Eq>
jh::conc::pointer_pool< T, Hash, Eq >::pointer_pool ( const pointer_pool< T, Hash, Eq > & )
delete

Deleted copy constructor.

Copying is disabled because two pools observing the same set of immutable objects have no meaningful deduplication relationship. A duplicated observer would only increase contention and break the one-pool-per-type principle for resource or handle management.

Note
For structurally immutable resource types, only one pool should exist in the entire program. For simple data deduplication (e.g. shared strings), threads can directly share std::reference_wrapper or shared_ptr instead of copying the pool.

◆ pointer_pool() [3/3]

template<typename T, typename Hash, typename Eq>
jh::conc::pointer_pool< T, Hash, Eq >::pointer_pool ( pointer_pool< T, Hash, Eq > && other)
inlinenoexcept

Move constructor.

Transfers internal weak references and capacity state from another pool. The source pool is locked during transfer and cleared afterward to ensure a valid empty state.

Note
Moving a pool transfers observation authority, not ownership of objects. Since the pool tracks objects via weak_ptr, live instances remain valid and unaffected. After a move, entries may temporarily exist in both the old and new pool, but this is acceptable for deduplication use.

Member Function Documentation

◆ acquire() [1/2]

template<typename T, typename Hash, typename Eq>
template<typename... Args>
std::shared_ptr< T > jh::conc::pointer_pool< T, Hash, Eq >::acquire ( Args &&... args)
inline

Retrieves an object from the pool, or creates a new one if none exists.

Constructs a temporary instance of T using the forwarded arguments, then attempts to insert it into the pool. If a logically equivalent instance already exists (as determined by Eq), it is reused and the newly created temporary object is discarded. Otherwise, the new instance is inserted and returned.

Template Parameters
ArgsConstructor argument types for T.
Parameters
argsArguments forwarded to T's constructor.
Returns
A std::shared_ptr<T> representing the pooled object.

Acquisition Flow:

  1. A new object is tentatively constructed using the forwarded arguments.
  2. The pool lock is acquired only during insertion and lookup.
  3. If a logically equivalent instance already exists, it is reused โ€” the temporary object is immediately discarded.
  4. If not found, the new object is inserted and its shared_ptr returned.
Note
The pool employs a construct-first, lock-then-insert model. This avoids holding the pool lock during object construction, enabling support for non-copyable or non-movable types. Temporary objects may be discarded if an equivalent instance already exists, so types should support lightweight provisional construction (e.g. lazy initialization of heavy internal resources).

◆ acquire() [2/2]

template<typename T, typename Hash, typename Eq>
template<typename... Args>
std::shared_ptr< T > jh::conc::pointer_pool< T, Hash, Eq >::acquire ( Args &&... args) const
delete

Deleted acquire() for const pointer_pool.

Prevents acquiring or inserting objects through a constant pool reference. Since acquisition may modify the internal pool state, it is not permitted on const instances.

Template Parameters
ArgsConstructor argument types for T.
Note
This overload exists purely to provide a compile-time diagnostic when acquire() is accidentally called on a constant pool reference. Immutable access to existing shared objects should be performed through previously returned std::shared_ptr instances.

◆ capacity()

template<typename T, typename Hash, typename Eq>
std::uint64_t jh::conc::pointer_pool< T, Hash, Eq >::capacity ( ) const
inlinenodiscard

Gets the current reserved size of the pool.

Returns
The reserved size limit before expansion or contraction.

◆ cleanup()

template<typename T, typename Hash, typename Eq>
void jh::conc::pointer_pool< T, Hash, Eq >::cleanup ( )
inline

Removes expired weak references from the pool.

Scans the internal container and erases all weak_ptr entries that have expired (that is, their corresponding shared_ptr instances have been destroyed).

This operation reclaims hash table slots and prevents unbounded growth when many pooled objects are released.

Note
This function is safe to call at any time and is intended for manual maintenance. Automatic cleanup also occurs opportunistically during insertion or expansion when capacity thresholds are reached.

◆ cleanup_shrink()

template<typename T, typename Hash, typename Eq>
void jh::conc::pointer_pool< T, Hash, Eq >::cleanup_shrink ( )
inline

Removes expired entries and conditionally shrinks the reserved capacity.

Performs the same expired-entry cleanup as cleanup(), then evaluates the current usage ratio to determine whether capacity should be reduced.

If the number of active entries falls below 0.25 of the current reserved size (the low-watermark ratio), the reserved capacity is reduced to one half of its previous value. The pool will never shrink below MIN_RESERVED_SIZE.

Note
  • Both manual and automatic shrinkage follow the same rule: capacity is reduced by half instead of being minimized to fit the current usage exactly.
  • This conservative policy prevents oscillation between expansion and contraction when workload size fluctuates, reducing allocation jitter.
  • Since a previously expanded pool indicates historically higher load, shrinking only halfway preserves readiness for future reuse without significant memory overhead.

This function is intended for manual maintenance when predictable memory release is desired. It complements the automatic, event-driven resizing that may also perform half shrinkage during expansion checks.

◆ clear()

template<typename T, typename Hash, typename Eq>
void jh::conc::pointer_pool< T, Hash, Eq >::clear ( )
inline

Clears all entries and resets the pool to its initial state.

Removes all elements from the internal container and resets capacity_ to MIN_RESERVED_SIZE. This operation is functionally equivalent to clear() on standard containers, but is thread-safe and ensures consistent internal state for concurrent environments.

Because the pool only stores weak_ptr references, clearing it merely removes observation records and does not affect the lifetime of externally held shared_ptr instances. For immutable data types, deduplication integrity remains intact.

Note
  • For structurally immutable resource or handle pools, calling clear() is not recommended, as it abandons tracking of active handles and may cause side effects.
  • After clearing, capacity() is reset to MIN_RESERVED_SIZE, fully restoring the pool to its initial baseline.
  • Unlike move operations, which preserve capacity to prevent unnecessary re-expansion, clear() always resets the capacity to its minimum for a deterministic clean state.

◆ operator=() [1/2]

template<typename T, typename Hash, typename Eq>
pointer_pool & jh::conc::pointer_pool< T, Hash, Eq >::operator= ( const pointer_pool< T, Hash, Eq > & )
delete

Deleted copy assignment operator.

Copy assignment is not supported for the same reasons as copy construction: duplicating the pool would create two independent observers of the same logical object space, defeating deduplication semantics.

◆ operator=() [2/2]

template<typename T, typename Hash, typename Eq>
pointer_pool & jh::conc::pointer_pool< T, Hash, Eq >::operator= ( pointer_pool< T, Hash, Eq > && other)
inlinenoexcept

Move assignment operator.

Moves the weak reference set and capacity state from another pool. Both pools are locked during transfer to ensure atomicity.

Note
During move assignment, all existing weak references in the current pool are released and replaced by those from the source pool. This only removes the pool's observation of those objects โ€” their actual lifetimes remain intact because ownership is held by external std::shared_ptr instances.
Moving represents transfer of observation scope. Since deduplication is tolerant to transient duplicates, the existence of similar entries in both pools after move is not a correctness issue. The moved-from pool is cleared and remains valid for later reuse.
Warning
For structurally immutable resource or handle pools, move assignment is not semantically appropriate, as such pools are expected to be unique within their management domain.

◆ size()

template<typename T, typename Hash, typename Eq>
std::uint64_t jh::conc::pointer_pool< T, Hash, Eq >::size ( ) const
inlinenodiscard

Gets the current number of elements in the pool.

Returns
The number of stored weak_ptrs (including expired ones).

Member Data Documentation

◆ MIN_RESERVED_SIZE

template<typename T, typename Hash, typename Eq>
std::uint64_t constexpr jh::conc::pointer_pool< T, Hash, Eq >::MIN_RESERVED_SIZE = 16
staticconstexpr

The minimum reserved capacity for the pool.

Defines the lower bound of the adaptive capacity management system. The pool will never shrink below this threshold even when mostly empty, ensuring predictable memory usage and avoiding excessive reallocation.

Note
This value is also used as the default reserve size when constructing a new pool.

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