DNDSR 0.1.0.dev1+gcd065ad
Distributed Numeric Data Structure for CFV
Loading...
Searching...
No Matches
ObjectPool.hpp
Go to the documentation of this file.
1#pragma once
2/// @file ObjectPool.hpp
3/// @brief Pre-allocated object pool with RAII checkout/return semantics.
4
5// #include <unordered_set>
6#include "Defines.hpp"
7
8namespace DNDS
9{
10 /**
11 * @brief Generic object pool: caches `unique_ptr<T>` instances and hands them
12 * out with RAII return-on-destruction semantics.
13 *
14 * @details Intended for expensive-to-construct objects used in hot loops
15 * (e.g., Eigen solvers, pre-allocated scratch matrices). Typical pattern:
16 * ```cpp
17 * ObjectPool<SomeWorker> pool;
18 * pool.resize(nThreads, ctorArgs...);
19 * {
20 * auto handle = pool.get(); // returns from pool, or empty if exhausted
21 * handle->doWork();
22 * } // handle goes out of scope -> worker returned to pool
23 * ```
24 *
25 * The pool shares ownership with every outstanding @ref DNDS::ObjectPoolAllocated "ObjectPoolAllocated" via
26 * a `shared_ptr<Pool>`; workers correctly skip the return step if the
27 * pool itself has been destroyed meanwhile.
28 *
29 * @tparam T Pooled object type.
30 */
31 template <class T = int>
33 {
34 public:
35 using uPtrResource = std::unique_ptr<T>;
36 /// @brief Internal storage shared among all handles.
37 struct Pool
38 {
39 std::vector<uPtrResource> _pool;
40 /// @brief Push an object back onto the free-list.
42 {
43 _pool.emplace_back(std::move(p));
44 }
45 };
46 /// @brief Shared pointer to the underlying storage.
47 std::shared_ptr<Pool> pPool;
48
49 public:
50 /// @brief Construct an empty pool; populate with #resize.
52 {
53 pPool = std::make_shared<Pool>();
54 }
55
56 /// @brief Number of objects currently available (not checked out).
57 size_t size()
58 {
59 return pPool->_pool.size();
60 }
61
62 /**
63 * @brief RAII handle returned by @ref DNDS::ObjectPool "ObjectPool"::get (and friends).
64 *
65 * @details Behaves like a `unique_ptr<T>`; in its destructor it
66 * returns the managed object to the pool (or drops it if the pool
67 * is already gone).
68 */
70 {
71 uPtrResource _ptr;
72 std::weak_ptr<Pool> pool;
73
74 public:
75 ObjectPoolAllocated(uPtrResource n_ptr, std::shared_ptr<Pool> &pPool)
76 {
77 DNDS_assert_info(pPool, "the original pool is invalid!");
78 pool = pPool;
79 _ptr = std::move(n_ptr);
80 }
82 {
83 _ptr = std::move(R._ptr);
84 pool = std::move(R.pool);
85 }
86 void operator=(ObjectPoolAllocated &&R) noexcept
87 {
88 _ptr = std::move(R._ptr);
89 pool = std::move(R.pool);
90 }
91 T &operator*() { return *_ptr; }
92 const T &operator*() const { return *_ptr; }
93 /// @brief `true` if the handle holds an object.
94 operator bool() const { return bool(_ptr); }
95 /// @brief Arrow-access to the underlying `unique_ptr` (lets callers
96 /// reach through it to `T`).
97 uPtrResource &operator->() { return _ptr; }
99 {
100 auto poolLocked = pool.lock();
101 if (poolLocked && _ptr)
102 poolLocked->_pool.emplace_back(std::move(_ptr));
103 }
104 };
105
106 /// @brief Pre-allocate `N` objects, forwarding `__ctorArgs` to each ctor.
107 /// Clears any previously pooled instances.
108 template <class... _CtorArgs>
109 void resize(size_t N, _CtorArgs &&...__ctorArgs)
110 {
111 pPool->_pool.clear();
112 pPool->_pool.reserve(N);
113 while (pPool->_pool.size() < N)
114 {
115 uPtrResource p = std::make_unique<T>(std::forward<_CtorArgs>(__ctorArgs)...);
116 pPool->_pool.emplace_back(std::move(p));
117 }
118 }
119
120 /// @brief Like #resize but calls `FInit(obj)` on each newly-created object.
121 template <class TFInit, class... _CtorArgs>
122 void resizeInit(size_t N, TFInit &&FInit, _CtorArgs &&...__ctorArgs)
123 {
124 pPool->_pool.clear();
125 pPool->_pool.reserve(N);
126 while (pPool->_pool.size() < N)
127 {
128 uPtrResource p = std::make_unique<T>(std::forward<_CtorArgs>(__ctorArgs)...);
129 FInit(*p);
130 pPool->_pool.emplace_back(std::move(p));
131 }
132 }
133
134 /// @brief Take an object out of the pool. Returns an empty handle if exhausted.
136 {
137 if (pPool->_pool.size())
138 {
139 auto ret = ObjectPoolAllocated(std::move(pPool->_pool.back()), pPool);
140 pPool->_pool.pop_back();
141 return ret;
142 }
143 return ObjectPoolAllocated(uPtrResource(), pPool); // empty if no resource left
144 }
145
146 /// @brief Like #get, but allocates a brand-new object if the pool is empty.
147 template <class... _CtorArgs>
148 ObjectPoolAllocated getAlloc(_CtorArgs &&...__ctorArgs)
149 {
150 if (pPool->_pool.size())
151 {
152 auto ret = ObjectPoolAllocated(std::move(pPool->_pool.back()), pPool);
153 pPool->_pool.pop_back();
154 return ret;
155 }
156 uPtrResource p = std::make_unique<T>(std::forward<_CtorArgs>(__ctorArgs)...);
157 return ObjectPoolAllocated(std::move(p), pPool);
158 }
159
160 /// @brief Like #getAlloc, but additionally runs `FInit(obj)` on newly-allocated objects.
161 template <class TFInit, class... _CtorArgs>
162 ObjectPoolAllocated getAllocInit(TFInit &&FInit, _CtorArgs &&...__ctorArgs)
163 {
164 if (pPool->_pool.size())
165 {
166 auto ret = ObjectPoolAllocated(std::move(pPool->_pool.back()), pPool);
167 pPool->_pool.pop_back();
168 return ret;
169 }
170 uPtrResource p = std::make_unique<T>(std::forward<_CtorArgs>(__ctorArgs)...);
171 FInit(*p);
172 return ObjectPoolAllocated(std::move(p), pPool);
173 }
174 };
175}
Core type aliases, constants, and metaprogramming utilities for the DNDS framework.
#define DNDS_assert_info(expr, info)
Debug-only assertion with an extra std::string info message.
Definition Errors.hpp:113
RAII handle returned by ObjectPoolget (and friends).
ObjectPoolAllocated(uPtrResource n_ptr, std::shared_ptr< Pool > &pPool)
void operator=(ObjectPoolAllocated &&R) noexcept
uPtrResource & operator->()
Arrow-access to the underlying unique_ptr (lets callers reach through it to T).
ObjectPoolAllocated(ObjectPoolAllocated &&R) noexcept
Generic object pool: caches unique_ptr<T> instances and hands them out with RAII return-on-destructio...
void resize(size_t N, _CtorArgs &&...__ctorArgs)
Pre-allocate N objects, forwarding __ctorArgs to each ctor. Clears any previously pooled instances.
ObjectPoolAllocated get()
Take an object out of the pool. Returns an empty handle if exhausted.
size_t size()
Number of objects currently available (not checked out).
ObjectPoolAllocated getAlloc(_CtorArgs &&...__ctorArgs)
Like get, but allocates a brand-new object if the pool is empty.
std::unique_ptr< T > uPtrResource
void resizeInit(size_t N, TFInit &&FInit, _CtorArgs &&...__ctorArgs)
Like resize but calls FInit(obj) on each newly-created object.
ObjectPool()
Construct an empty pool; populate with resize.
ObjectPoolAllocated getAllocInit(TFInit &&FInit, _CtorArgs &&...__ctorArgs)
Like getAlloc, but additionally runs FInit(obj) on newly-allocated objects.
std::shared_ptr< Pool > pPool
Shared pointer to the underlying storage.
the host side operators are provided as implemented
Internal storage shared among all handles.
void recycle(uPtrResource p)
Push an object back onto the free-list.
std::vector< uPtrResource > _pool
constexpr DNDS::index N