Template Class PoolAllocator¶
Defined in File pool_allocator.h
Class Documentation¶
-
template<typename T, size_t C, typename L = SpinLock>
class PoolAllocator¶ Fixed-size object pool with O(1) allocation/deallocation using embedded freelist.
PoolAllocator pre-allocates a fixed buffer for C instances of type T and manages them using a freelist embedded directly in the free blocks. This makes allocation and deallocation O(1) pointer operations with LIFO reuse (last freed is first allocated), which tends to have good cache characteristics.
The freelist works by storing pointers to the next free block inside each free block itself. This is why the constraint
sizeof(T) >= sizeof(void*)exists: each free block must be large enough to hold a pointer to the next free block. Types smaller than a pointer (like bool, char, uint16_t on 64-bit systems) cannot be pooled with this allocator.Thread Safety: Controlled by template parameter L. By default uses SpinLock, which is ideal for very short-lived locks (microseconds) with rare contention. For longer critical sections or higher contention, substitute std::mutex:
PoolAllocator<MyType, 1000, std::mutex>.Example - Particle system with object pooling:
PoolAllocator<Particle, 1000> particle_pool; void spawn_particle(Vector3 pos, Vector3 vel) { auto* particle = particle_pool.alloc(pos, vel); // O(1) allocation active_particles.push_back(particle); } void update_particles(float dt) { for (auto it = active_particles.begin(); it != active_particles.end();) { (*it)->update(dt); if ((*it)->is_dead()) { particle_pool.free(*it); // O(1) deallocation, returns to freelist it = active_particles.erase(it); } else { ++it; } } }
Important Notes:
Allocation throws std::bad_alloc when pool is exhausted (all C slots used)
clear() rebuilds freelist WITHOUT calling destructors - only use when pool is empty or all objects are trivially destructible
Freelist reuse is LIFO order (last freed, first allocated)
Pool memory is never deallocated until PoolAllocator is destroyed
See also
BucketPoolAllocator for variable-sized allocations up to a bucket size
See also
SpinLock for the default lock implementation
- Template Parameters:
T – The object type to pool. Must satisfy
sizeof(T) >= sizeof(void*)because free blocks embed next-pointers in themselves. Common pooled types: game entities, particles, messages, temporary objects.C – Pool capacity (maximum number of T instances). Pool size is C * sizeof(T). Set based on worst-case usage to avoid exhaustion.
L – Lock type for thread safety. Defaults to SpinLock. Use std::mutex for longer critical sections or high contention. Use a no-op lock type for single-threaded scenarios to eliminate locking overhead.
Public Functions
-
inline PoolAllocator() noexcept¶
-
template<typename ...Args>
inline T *alloc(Args&&... args)¶ Allocates and constructs a new object of type T
- Template Parameters:
Args – Constructor argument types
- Parameters:
args – Constructor arguments
- Returns:
Pointer to the new object
-
inline void clear()¶
Rebuilds the freelist, making all pool slots available again.
WARNING: This does NOT call destructors on allocated objects. Calling clear() with active allocations causes RESOURCE LEAKS and UNDEFINED BEHAVIOR for types managing resources (heap memory, file handles, mutexes, etc.). This is a CORRECTNESS and SAFETY issue, not merely a performance concern.
Only use clear() when:
The pool is completely empty (all objects freed), OR
All allocated objects are trivially destructible (no resources to clean up)
For proper cleanup, call free() on ALL allocated objects before clear().