|
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.
|
Pointer-based interning for non-copyable, non-movable, structurally immutable objects. More...
#include <atomic>#include <cstdint>#include <algorithm>#include <vector>#include <unordered_set>#include <mutex>#include <memory>#include <shared_mutex>#include "jh/synchronous/strong_lock.h"Go to the source code of this file.
Classes | |
| class | jh::conc::pointer_pool< T, Hash, Eq > |
| Weak pointer-observed pool for immutable or structurally immutable objects. More... | |
Namespaces | |
| namespace | jh::conc |
| Aggregated namespace for concurrency-aware resource containers. | |
Pointer-based interning for non-copyable, non-movable, structurally immutable objects.
jh::conc::pointer_pool is a weak-observed pointer interning container designed for objects whose identity is defined intrinsically by the object itself and which cannot be copied, moved, or represented by an external key. These objects are stored and deduplicated through std::shared_ptr instances, while the pool maintains only std::weak_ptr references for lookup and reuse.
The pool specializes in handling objects that must reside at a stable address for their entire lifetime, and therefore cannot be placed inside contiguous storage or trivial containers. It enables pointer-stable sharing without imposing ownership, allocator customization, or intrusive hooks.
The essential purpose of pointer_pool is to support types that are impossible to intern through traditional contiguous or key-indexed structures. These types may be non-copyable, non-movable, or may express equality only through their full object state rather than an external key. As a result, they cannot participate in compact storage models and must rely on pointer identity for stable lifetime management.
Because the pool only ever stores separate heap-allocated shared objects, fragmentation is unavoidable. For this reason, the pool deliberately avoids allocator customization. Regardless of allocator choice, large numbers of pointer-sized allocations inevitably produce fragmentation, and avoiding it is neither practical nor a design goal for this container.
The pool does not provide a find() operation. This is a fundamental design decision. Equality and hashing depend on the object itself, so a candidate object must already exist before the pool can determine whether an equivalent instance is present. Therefore, every acquisition follows the sequence:
This model ensures that objects can be deduplicated even when they offer no external key. However, it also means that construction must always occur before lookup. As such, the theoretical access cost is a combination of O(1) hash addressing and whatever cost is required to build a provisional object.
Because provisional construction may occur frequently, objects used with pointer_pool should support low-cost identity construction. Heavy initialization should be deferred until after the object becomes the accepted canonical instance. A common pattern is:
std::once_flag. This approach allows the pool to discard temporary instances cheaply while ensuring that full initialization happens only for the accepted canonical object.
The pool never owns any object. All objects are owned exclusively by std::shared_ptr instances returned to the user. The pool only observes these objects via std::weak_ptr.
Because of this design:
Cleanup is performed on a best-effort basis. The pool removes expired weak entries only during insertion, expansion, or explicit calls to cleanup() and cleanup_shrink().
Resizing and shrinking are adaptive:
This design avoids rehash jitter and minimizes allocation disturbances during periods of high-frequency reuse.
The pool is intended for objects that:
flat_pool (resource_pool)pointer_pool differs fundamentally from flat_pool and its user-layer extension resource_pool. While pointer_pool is designed for immovable, non-copyable objects that cannot be represented by an external key, flat_pool supports contiguous memory layout, slot reuse, and key-driven lookup. It offers find() operations that return a null pointer on miss and can store either keys alone or key-value pairs. Lookup is based on full-hash binary search with O(log N) complexity.
Although flat_pool has asymptotically higher lookup complexity than the O(1) expectation of hash probing, this does not imply inferior performance. Binary search over contiguous memory is extremely cache-friendly, and modern CPUs can predict the comparison pattern effectively due to stable branching behavior. Even without branch prediction, the number of steps is small and bounded. By contrast, hash-table probing involves irregular memory access and higher constant-time factors despite its theoretical O(1) model. As a result, at small and medium scales, flat_pool lookup performance can match or exceed that of pointer_pool. In reality, excessive fragmentation and pointer addressing that may fall into L3 are unacceptable in practical applications, pointer_pool will never be used in large-scale storage, so it can be simply assumed that flat_pool is faster and can store larger amounts of data.
flat_pool requires objects to be copyable or movable and can integrate allocator customization. pointer_pool remains the preferred structure for objects that cannot be relocated or keyed externally and must be deduplicated solely through their own equality semantics.
1.4.x
2025