libxr
Embedded C++ framework for bare-metal, RTOS and Linux
Loading...
Searching...
No Matches
object_pool.hpp
1#pragma once
2
3#include <atomic>
4#include <cstddef>
5#include <cstdint>
6#include <limits>
7#include <type_traits>
8#include <utility>
9
10#include "libxr_def.hpp"
11
12namespace LibXR
13{
45template <typename Data>
47{
48 public:
49 using ValueType = Data;
50 using IndexType = uint32_t;
51
58 class Slot
59 {
60 public:
63 requires std::is_default_constructible_v<Data>
64 = default;
65
70 template <typename... Args>
71 requires std::is_constructible_v<Data, Args...>
72 explicit Slot(std::in_place_t, Args&&... args) : data_(std::forward<Args>(args)...)
73 {
74 }
75
76 Slot(const Slot&) = delete;
77 Slot& operator=(const Slot&) = delete;
78 Slot(Slot&&) = delete;
79 Slot& operator=(Slot&&) = delete;
80
81 private:
82 friend class ObjectPool;
83 std::atomic<uint32_t> references_{0};
84 IndexType next_ = 0;
85 Data data_;
86 };
87
88 public:
89 class ConstHandle;
90
100 class Handle
101 {
102 public:
104 Handle() = default;
105
107 Handle(const Handle&) = delete;
109 Handle& operator=(const Handle&) = delete;
110
112 Handle(Handle&& other) noexcept
113 : pool_(std::exchange(other.pool_, nullptr)),
114 index_(std::exchange(other.index_, IndexType{}))
115 {
116 }
117
119 Handle& operator=(Handle&& other) noexcept
120 {
121 if (this != &other)
122 {
123 Reset();
124 pool_ = std::exchange(other.pool_, nullptr);
125 index_ = std::exchange(other.index_, IndexType{});
126 }
127 return *this;
128 }
129
131 ~Handle() { Reset(); }
132
134 [[nodiscard]] bool Valid() const { return pool_ != nullptr; }
135
137 [[nodiscard]] Data& Get()
138 {
139 ASSERT(Valid());
140 return pool_->slots_[index_].data_;
141 }
142
145 [[nodiscard]] const Data& Get() const
146 {
147 ASSERT(Valid());
148 return pool_->slots_[index_].data_;
149 }
150
152 [[nodiscard]] Data* operator->() { return &Get(); }
154 [[nodiscard]] const Data* operator->() const { return &Get(); }
156 [[nodiscard]] Data& operator*() { return Get(); }
158 [[nodiscard]] const Data& operator*() const { return Get(); }
159
161 [[nodiscard]] IndexType Index() const
162 {
163 ASSERT(Valid());
164 return index_;
165 }
166
169 void Reset()
170 {
171 ObjectPool* pool = std::exchange(pool_, nullptr);
172 const IndexType index = std::exchange(index_, IndexType{});
173 if (pool != nullptr)
174 {
175 pool->Release(index);
176 }
177 }
178
179 private:
180 friend class ObjectPool;
181 friend class ConstHandle;
182
183 Handle(ObjectPool* pool, IndexType index) : pool_(pool), index_(index) {}
184
185 ObjectPool* pool_ = nullptr;
186 IndexType index_ = {};
187 };
188
200 {
201 public:
203 ConstHandle() = default;
204
206 ConstHandle(const ConstHandle& other) noexcept
207 : pool_(other.pool_), index_(other.index_)
208 {
209 if (pool_ != nullptr)
210 {
211 pool_->Retain(index_);
212 }
213 }
214
216 ConstHandle(ConstHandle&& other) noexcept
217 : pool_(std::exchange(other.pool_, nullptr)),
218 index_(std::exchange(other.index_, IndexType{}))
219 {
220 }
221
224 ConstHandle(Handle&& writer) noexcept
225 : pool_(std::exchange(writer.pool_, nullptr)),
226 index_(std::exchange(writer.index_, IndexType{}))
227 {
228 }
229
232 ConstHandle& operator=(const ConstHandle& other) noexcept
233 {
234 if (pool_ != other.pool_ || index_ != other.index_)
235 {
236 ConstHandle copy(other);
237 Swap(copy);
238 }
239 return *this;
240 }
241
244 {
245 if (this != &other)
246 {
247 ConstHandle moved(std::move(other));
248 Swap(moved);
249 }
250 return *this;
251 }
252
255 ConstHandle& operator=(Handle&& writer) noexcept
256 {
257 ConstHandle moved(std::move(writer));
258 Swap(moved);
259 return *this;
260 }
261
265
267 [[nodiscard]] bool Valid() const { return pool_ != nullptr; }
268
271 [[nodiscard]] const Data& Get() const
272 {
273 ASSERT(Valid());
274 return pool_->slots_[index_].data_;
275 }
276
278 [[nodiscard]] const Data* operator->() const { return &Get(); }
280 [[nodiscard]] const Data& operator*() const { return Get(); }
281
283 [[nodiscard]] IndexType Index() const
284 {
285 ASSERT(Valid());
286 return index_;
287 }
288
291 void Reset()
292 {
293 ObjectPool* pool = std::exchange(pool_, nullptr);
294 const IndexType index = std::exchange(index_, IndexType{});
295 if (pool != nullptr)
296 {
297 pool->Release(index);
298 }
299 }
300
301 private:
302 void Swap(ConstHandle& other) noexcept
303 {
304 std::swap(pool_, other.pool_);
305 std::swap(index_, other.index_);
306 }
307
308 ObjectPool* pool_ = nullptr;
309 IndexType index_ = {};
310 };
311
316 template <typename T = Data>
317 requires std::is_default_constructible_v<T>
318 explicit ObjectPool(size_t slot_count)
319 : slots_(new Slot[slot_count]), slot_count_(slot_count), owns_slots_(true)
320 {
321 InitializeFreeStack();
322 }
323
334 ObjectPool(size_t slot_count, Slot* slots)
335 : slots_(slots), slot_count_(slot_count), owns_slots_(false)
336 {
337 ASSERT(slots_ != nullptr);
338 InitializeFreeStack();
339 }
340
345 {
346 ASSERT(EmptySize() == slot_count_);
347
348 if (owns_slots_)
349 {
350 delete[] slots_;
351 }
352 }
353
354 ObjectPool(const ObjectPool&) = delete;
355 ObjectPool& operator=(const ObjectPool&) = delete;
356 ObjectPool(ObjectPool&&) = delete;
357 ObjectPool& operator=(ObjectPool&&) = delete;
358
368 [[nodiscard]] ErrorCode Acquire(Handle& handle)
369 {
370 ASSERT(!handle.Valid());
371#ifdef LIBXR_DEBUG_BUILD
372 // 重叠申请会让空闲栈出现 ABA,同一槽位被两个申请方取得。
373 // Overlapping acquisitions cause ABA on the free stack and hand one slot to two
374 // acquirers.
375 uint32_t idle = 0U;
376 ASSERT(acquiring_.compare_exchange_strong(idle, 1U, std::memory_order_acquire,
377 std::memory_order_relaxed));
378#endif
379
380 IndexType index = 0;
381 const ErrorCode ec = PopFree(index);
382 if (ec == ErrorCode::OK)
383 {
384 free_count_.fetch_sub(1U, std::memory_order_relaxed);
385 DEV_ASSERT(slots_[index].references_.load(std::memory_order_relaxed) == 0U);
386 slots_[index].references_.store(1U, std::memory_order_relaxed);
387 handle = Handle(this, index);
388 }
389
390#ifdef LIBXR_DEBUG_BUILD
391 acquiring_.store(0U, std::memory_order_release);
392#endif
393 return ec;
394 }
395
402 [[nodiscard]] size_t EmptySize() const
403 {
404 return free_count_.load(std::memory_order_relaxed);
405 }
406
411 [[nodiscard]] size_t Size() const { return slot_count_; }
412
421 [[nodiscard]] Data& UnsafeAt(size_t index)
422 {
423 ASSERT(index < slot_count_);
424 return slots_[index].data_;
425 }
426
435 [[nodiscard]] const Data& UnsafeAt(size_t index) const
436 {
437 ASSERT(index < slot_count_);
438 return slots_[index].data_;
439 }
440
441 private:
442 static constexpr IndexType EMPTY_INDEX = std::numeric_limits<IndexType>::max();
443
445 void Retain(IndexType index) noexcept
446 {
447 DEV_ASSERT(static_cast<size_t>(index) < slot_count_);
448 [[maybe_unused]] const uint32_t previous =
449 slots_[index].references_.fetch_add(1U, std::memory_order_relaxed);
450 DEV_ASSERT(previous > 0U);
451 ASSERT(previous < std::numeric_limits<uint32_t>::max());
452 }
453
454 void Release(IndexType index)
455 {
456 DEV_ASSERT(static_cast<size_t>(index) < slot_count_);
457 // acq_rel:其他持有者的负载访问先于最终归还。
458 // acq_rel: other holders' payload accesses happen before the final return.
459 const uint32_t previous =
460 slots_[index].references_.fetch_sub(1U, std::memory_order_acq_rel);
461 DEV_ASSERT(previous > 0U);
462 if (previous == 1U)
463 {
464 free_count_.fetch_add(1U, std::memory_order_relaxed);
465 PushFree(index);
466 }
467 }
468
469 void InitializeFreeStack()
470 {
471 ASSERT(slot_count_ > 0);
472 ASSERT(slot_count_ < static_cast<size_t>(EMPTY_INDEX));
473 for (size_t index = 0; index < slot_count_; ++index)
474 {
475 slots_[index].next_ = static_cast<IndexType>(index + 1U);
476 }
477 slots_[slot_count_ - 1U].next_ = EMPTY_INDEX;
478 free_head_.store(0U, std::memory_order_relaxed);
479 free_count_.store(static_cast<uint32_t>(slot_count_), std::memory_order_relaxed);
480 }
481
484 ErrorCode PopFree(IndexType& index)
485 {
486 IndexType head = free_head_.load(std::memory_order_acquire);
487 IndexType next = EMPTY_INDEX;
488 do
489 {
490 if (head == EMPTY_INDEX)
491 {
492 return ErrorCode::EMPTY;
493 }
494 DEV_ASSERT(static_cast<size_t>(head) < slot_count_);
495 // 只有申请方会取出槽位,head 在栈上时其 next_ 不会被改写,所以没有 ABA。
496 // Only the acquirer removes slots, so next_ of a stacked head cannot change and
497 // ABA cannot occur.
498 next = slots_[head].next_;
499 } while (!free_head_.compare_exchange_weak(head, next, std::memory_order_acquire,
500 std::memory_order_acquire));
501 index = head;
502 return ErrorCode::OK;
503 }
504
507 void PushFree(IndexType index)
508 {
509 IndexType head = free_head_.load(std::memory_order_relaxed);
510 do
511 {
512 // 槽位已不在栈上且只属于本次归还,可以直接写 next_。
513 // The slot is off the stack and owned by this return, so next_ is private here.
514 slots_[index].next_ = head;
515 } while (!free_head_.compare_exchange_weak(head, index, std::memory_order_release,
516 std::memory_order_relaxed));
517 }
518
519 Slot* slots_;
520 const size_t slot_count_;
521 const bool owns_slots_;
522 std::atomic<IndexType> free_head_{EMPTY_INDEX};
523 std::atomic<uint32_t> free_count_{0};
527 std::atomic<uint32_t> acquiring_{0};
528};
529} // namespace LibXR
只读句柄:共享一个槽位,可以复制。 Read-only handle: shares one slot, copyable.
ConstHandle(ConstHandle &&other) noexcept
转移引用并清空源句柄 / Transfer the reference and empty the source.
~ConstHandle()
释放本引用;最后一个引用归还槽位 / Release; the final reference returns the slot.
ConstHandle & operator=(const ConstHandle &other) noexcept
复制赋值:先保留新引用再释放旧引用 / Copy-assign: retain the new reference before releasing the old one.
bool Valid() const
是否持有引用 / Whether this handle owns a reference.
const Data & Get() const
只读访问负载,句柄必须有效 / Read-only payload access; requires a valid handle.
const Data * operator->() const
返回只读负载指针 / Return a read-only payload pointer.
ConstHandle(const ConstHandle &other) noexcept
复制引用 / Copy the reference.
const Data & operator*() const
只读解引用 / Dereference read-only.
void Reset()
清空句柄并释放引用;空句柄无操作 / Empty and release; no-op on an empty handle.
ConstHandle()=default
构造空句柄 / Construct an empty handle.
ConstHandle & operator=(Handle &&writer) noexcept
接过可写句柄的槽位并释放旧引用 / Take over a writable handle's slot and release the old reference.
ConstHandle & operator=(ConstHandle &&other) noexcept
转移赋值并释放旧引用 / Move-assign and release the old reference.
IndexType Index() const
返回有效句柄的槽索引 / Return the slot index of a valid handle.
ConstHandle(Handle &&writer) noexcept
接过可写句柄的槽位,源句柄变空 / Take over a writable handle's slot; the source becomes empty.
可写句柄:独占一个槽位,只能移动。 Writable handle: exclusive owner of one slot, move-only.
void Reset()
清空句柄并释放槽位;空句柄无操作 / Empty and release; no-op on an empty handle.
Data & operator*()
解引用 / Dereference.
IndexType Index() const
返回有效句柄的槽索引 / Return the slot index of a valid handle.
bool Valid() const
是否持有槽位 / Whether this handle owns a slot.
const Data * operator->() const
返回只读负载指针 / Return a read-only payload pointer.
~Handle()
释放槽位 / Release the slot.
Handle & operator=(const Handle &)=delete
禁止拷贝赋值 / Non-copy-assignable.
Handle(Handle &&other) noexcept
转移槽位并清空源句柄 / Transfer the slot and empty the source.
const Data & Get() const
只读访问负载,句柄必须有效 / Read-only payload access; requires a valid handle.
const Data & operator*() const
只读解引用 / Dereference read-only.
Handle & operator=(Handle &&other) noexcept
转移槽位并释放原有槽位 / Transfer the slot and release the previous one.
Data & Get()
访问负载,句柄必须有效 / Access the payload; requires a valid handle.
Handle()=default
构造空句柄 / Construct an empty handle.
Data * operator->()
返回负载指针 / Return the payload pointer.
Handle(const Handle &)=delete
禁止拷贝构造 / Non-copyable.
槽位:引用计数、空闲链接和常驻负载。 Slot: reference count, free-stack link and resident payload.
Slot()=default
默认构造负载 / Default-construct the payload.
带引用计数的固定槽对象池 / Fixed-slot object pool with reference counting.
size_t Size() const
返回槽位总数 / Return the total number of slots.
const Data & UnsafeAt(size_t index) const
按槽位索引只读访问对象,不检查所有权。 Read-only access by slot index without ownership checks.
ObjectPool(size_t slot_count, Slot *slots)
用外部槽数组构造池 / Construct the pool with caller-provided slots.
ObjectPool(size_t slot_count)
用内部槽数组构造池 / Construct the pool with internal slots.
size_t EmptySize() const
返回空闲槽位数 / Return the number of free slots.
Data ValueType
槽内对象类型 / Slot object type.
Data & UnsafeAt(size_t index)
按槽位索引直接访问对象,不检查所有权。 Access an object by slot index without ownership checks.
~ObjectPool()
析构池;所有句柄必须已释放 / Destroy the pool; all handles must be released.
uint32_t IndexType
槽索引类型 / Slot index type.
ErrorCode Acquire(Handle &handle)
获取一个空闲槽位 / Acquire one free slot.
LibXR 命名空间 / LibXR namespace.
Definition ch32_can.hpp:14
ErrorCode
定义错误码枚举 / Defines an enumeration for error codes
@ EMPTY
为空 / Empty
@ OK
操作成功 / Operation successful