Slick OrderBook 1.1.0
High-Performance C++23 OrderBook Library
Loading...
Searching...
No Matches
memory_pool.hpp
Go to the documentation of this file.
1// Copyright 2026 Slick Quant
2// SPDX-License-Identifier: MIT
3
4#pragma once
5
7#include <cstddef>
8#include <cstdint>
9#include <memory>
10#include <new>
11#include <type_traits>
12#include <vector>
13
15
33template<typename T>
35public:
36 static_assert(std::is_nothrow_destructible_v<T>,
37 "ObjectPool requires nothrow destructible types");
38 // Note: ObjectPool supports over-aligned types via std::align_val_t
39 // (see operator delete calls with std::align_val_t{alignof(T)})
40
43 explicit ObjectPool(std::size_t initial_capacity = 1024)
44 : capacity_(0), size_(0), free_list_(nullptr) {
45 if (initial_capacity > 0) {
46 reserve(initial_capacity);
47 }
48 }
49
51 ~ObjectPool() noexcept {
52 clear();
53 for (Block& block : blocks_) {
54 ::operator delete(block.memory, std::align_val_t{alignof(T)});
55 }
56 }
57
58 // Non-copyable
59 ObjectPool(const ObjectPool&) = delete;
60 ObjectPool& operator=(const ObjectPool&) = delete;
61
62 // Movable
63 ObjectPool(ObjectPool&& other) noexcept
64 : blocks_(std::move(other.blocks_)),
65 capacity_(other.capacity_),
66 size_(other.size_),
67 free_list_(other.free_list_) {
68 other.capacity_ = 0;
69 other.size_ = 0;
70 other.free_list_ = nullptr;
71 }
72
73 ObjectPool& operator=(ObjectPool&& other) noexcept {
74 if (this != &other) {
75 clear();
76 for (Block& block : blocks_) {
77 ::operator delete(block.memory, std::align_val_t{alignof(T)});
78 }
79 blocks_ = std::move(other.blocks_);
80 capacity_ = other.capacity_;
81 size_ = other.size_;
82 free_list_ = other.free_list_;
83 other.capacity_ = 0;
84 other.size_ = 0;
85 other.free_list_ = nullptr;
86 }
87 return *this;
88 }
89
93 [[nodiscard]] T* allocate() {
94 // Try to get from free list first
95 if (free_list_ != nullptr) {
96 FreeNode* node = free_list_;
97 free_list_ = node->next;
98 ++size_;
99 return reinterpret_cast<T*>(node);
100 }
101
102 // No free objects, need to grow the pool
103 if (!grow()) {
104 return nullptr;
105 }
106
107 // Try again from free list
108 if (free_list_ != nullptr) {
109 FreeNode* node = free_list_;
110 free_list_ = node->next;
111 ++size_;
112 return reinterpret_cast<T*>(node);
113 }
114
115 return nullptr;
116 }
117
121 template<typename... Args>
122 [[nodiscard]] T* construct(Args&&... args) {
123 T* ptr = allocate();
124 if (ptr != nullptr) {
125 try {
126 new (ptr) T(std::forward<Args>(args)...);
127 } catch (...) {
128 deallocate(ptr);
129 throw;
130 }
131 }
132 return ptr;
133 }
134
137 void deallocate(T* ptr) noexcept {
138 if (ptr == nullptr) {
139 return;
140 }
141
142 // Add to free list
143 FreeNode* node = reinterpret_cast<FreeNode*>(ptr);
144 node->next = free_list_;
145 free_list_ = node;
146 --size_;
147 }
148
151 void destroy(T* ptr) noexcept {
152 if (ptr != nullptr) {
153 ptr->~T();
154 deallocate(ptr);
155 }
156 }
157
161 bool reserve(std::size_t n) {
162 if (n <= capacity_) {
163 return true;
164 }
165
166 std::size_t to_allocate = n - capacity_;
167 return grow(to_allocate);
168 }
169
171 [[nodiscard]] std::size_t capacity() const noexcept {
172 return capacity_;
173 }
174
176 [[nodiscard]] std::size_t size() const noexcept {
177 return size_;
178 }
179
181 [[nodiscard]] std::size_t available() const noexcept {
182 return capacity_ - size_;
183 }
184
186 [[nodiscard]] bool empty() const noexcept {
187 return size_ == 0;
188 }
189
192 void clear() noexcept {
193 // Reset free list to include all objects from all blocks
194 free_list_ = nullptr;
195 for (Block& block : blocks_) {
196 for (std::size_t i = 0; i < block.count; ++i) {
197 T* obj = reinterpret_cast<T*>(
198 static_cast<char*>(block.memory) + i * sizeof(T));
199 FreeNode* node = reinterpret_cast<FreeNode*>(obj);
200 node->next = free_list_;
201 free_list_ = node;
202 }
203 }
204 size_ = 0;
205 }
206
207private:
209 struct FreeNode {
210 FreeNode* next;
211 };
212
213 static_assert(sizeof(FreeNode) <= sizeof(T),
214 "ObjectPool requires sizeof(T) >= sizeof(void*)");
215
217 struct Block {
218 void* memory; // Pointer to allocated memory
219 std::size_t count; // Number of objects in this block
220 };
221
225 bool grow(std::size_t min_count = 0) {
226 // Use exponential growth strategy
227 // Start with 64, double each time, up to 8192 per block
228 constexpr std::size_t kMinBlockSize = 64;
229 constexpr std::size_t kMaxBlockSize = 8192;
230
231 std::size_t block_size = kMinBlockSize;
232 if (!blocks_.empty()) {
233 block_size = std::min(blocks_.back().count * 2, kMaxBlockSize);
234 }
235
236 // Ensure we allocate at least min_count
237 if (block_size < min_count) {
238 block_size = min_count;
239 }
240
241 // Allocate new block
242 void* memory = ::operator new(block_size * sizeof(T),
243 std::align_val_t{alignof(T)},
244 std::nothrow);
245 if (memory == nullptr) {
246 return false;
247 }
248
249 // Add block to list
250 blocks_.push_back({memory, block_size});
251 capacity_ += block_size;
252
253 // Add all objects in this block to free list
254 for (std::size_t i = 0; i < block_size; ++i) {
255 T* obj = reinterpret_cast<T*>(
256 static_cast<char*>(memory) + i * sizeof(T));
257 FreeNode* node = reinterpret_cast<FreeNode*>(obj);
258 node->next = free_list_;
259 free_list_ = node;
260 }
261
262 return true;
263 }
264
265 std::vector<Block> blocks_; // List of allocated memory blocks
266 std::size_t capacity_; // Total capacity across all blocks
267 std::size_t size_; // Number of currently allocated objects
268 FreeNode* free_list_; // Head of free list
269};
270
Object pool for zero-allocation management of frequently created/destroyed objects.
void deallocate(T *ptr) noexcept
Deallocate an object (does not call destructor)
ObjectPool & operator=(const ObjectPool &)=delete
ObjectPool(const ObjectPool &)=delete
bool empty() const noexcept
Check if pool is empty.
T * construct(Args &&... args)
Construct an object in-place with arguments.
ObjectPool & operator=(ObjectPool &&other) noexcept
T * allocate()
Allocate an object from the pool Returns a pointer to uninitialized memory.
bool reserve(std::size_t n)
Reserve capacity for at least n objects.
~ObjectPool() noexcept
Destructor - frees all memory blocks.
void destroy(T *ptr) noexcept
Destroy and deallocate an object.
ObjectPool(std::size_t initial_capacity=1024)
Constructor - pre-allocates initial capacity.
ObjectPool(ObjectPool &&other) noexcept
std::size_t capacity() const noexcept
Get current capacity (total objects that can be stored)
std::size_t size() const noexcept
Get current size (objects currently allocated)
void clear() noexcept
Clear all allocated objects (calls destructors if any are alive) Warning: This assumes all objects ha...
std::size_t available() const noexcept
Get number of available objects in free list.
#define SLICK_DETAIL_NAMESPACE_BEGIN
Definition config.hpp:129
#define SLICK_DETAIL_NAMESPACE_END
Definition config.hpp:130