VLink  2.0.0
A high-performance communication middleware
object_pool.h
浏览该文件的文档.
1 /*
2  * Copyright (C) 2026 by Thun Lu. All rights reserved.
3  * Author: Thun Lu <thun.lu@zohomail.cn>
4  * Repo: https://github.com/thun-res/vlink
5  * _ __ __ _ __
6  * | | / / / / (_) ____ / /__
7  * | | / / / / / / / __ \ / //_/
8  * | |/ / / /___ / / / / / / / ,<
9  * |___/ /_____/ /_/ /_/ /_/ /_/|_|
10  *
11  * Licensed under the Apache License, Version 2.0 (the "License");
12  * you may not use this file except in compliance with the License.
13  * You may obtain a copy of the License at
14  *
15  * http://www.apache.org/licenses/LICENSE-2.0
16  *
17  * Unless required by applicable law or agreed to in writing, software
18  * distributed under the License is distributed on an "AS IS" BASIS,
19  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
20  * See the License for the specific language governing permissions and
21  * limitations under the License.
22  */
23 
24 /**
25  * @file object_pool.h
26  * @brief Thread-safe generic object pool with RAII handles and a tunable reset policy.
27  *
28  * @details
29  * @c ObjectPool<T> recycles previously allocated @c T instances so that hot paths avoid
30  * repeated heap traffic. Callers acquire an object through one of three entry points,
31  * use it, and then either let RAII return it automatically or hand it back manually.
32  *
33  * Acquisition variants:
34  *
35  * | Method | Returned handle | Returns to pool automatically |
36  * | -------------- | ---------------------------------- | ----------------------------------- |
37  * | @c get() | @c unique_ptr<T, PoolDeleter> | Yes, on @c unique_ptr destruction |
38  * | @c get_shared | @c shared_ptr<T> | Yes, on last @c shared_ptr release |
39  * | @c borrow() | Raw @c T* | No, caller must invoke @c give_back |
40  *
41  * Reset-policy decision table for the optional @c ResetCallback:
42  *
43  * | Policy | Reset on acquire | Reset on release | Typical use |
44  * | ------------------ | ---------------- | ---------------- | --------------------------------- |
45  * | @c kPolicyNone | No | No | Pure / stateless objects |
46  * | @c kPolicyRelease | No | Yes | Default; scrub on the return path |
47  * | @c kPolicyAcquire | Yes | No | Scrub right before use |
48  * | @c kPolicyBoth | Yes | Yes | Defence-in-depth scrubbing |
49  *
50  * Lifecycle of a pooled object:
51  *
52  * @verbatim
53  * factory ---> free list <----------+
54  * | acquire |
55  * v |
56  * in use ---> release --+ (reset on release if enabled)
57  * ^ |
58  * +--reset-on-acquire+
59  * @endverbatim
60  *
61  * @par Thread safety
62  * Every public method acquires the internal mutex so concurrent callers may
63  * interleave freely. Factory and reset callbacks run outside the mutex.
64  *
65  * @par Example
66  * @code
67  * auto pool = std::make_shared<vlink::ObjectPool<Buffer>>(
68  * []{ return std::make_unique<Buffer>(4096); },
69  * 4,
70  * 16,
71  * [](Buffer& buf){ buf.clear(); },
72  * vlink::ObjectPool<Buffer>::kPolicyRelease);
73  *
74  * {
75  * auto buf = pool->get();
76  * buf->write(payload.data(), payload.size());
77  * }
78  *
79  * Buffer* raw = pool->borrow();
80  * raw->write(payload.data(), payload.size());
81  * pool->give_back(raw);
82  * @endcode
83  */
84 
85 #pragma once
86 
87 #include <memory>
88 #include <mutex>
89 #include <utility>
90 #include <vector>
91 
92 #include "./functional.h"
93 #include "./macros.h"
94 
95 namespace vlink {
96 
97 /**
98  * @class ObjectPoolBase
99  * @brief Type-independent base of @c ObjectPool that owns counters, mutex and policy state.
100  *
101  * @details
102  * Keeps the shared bookkeeping for @c ObjectPool<T> in a single translation unit so that
103  * cold-path validation and error-throwing helpers do not need to be re-instantiated for
104  * every element type. Element storage and callbacks remain in the derived template.
105  */
107  public:
108  /**
109  * @brief Decides when (or whether) the user-supplied @c ResetCallback runs.
110  *
111  * @details
112  * Reset failures on the release path are caught and the offending object is dropped from
113  * the pool instead of being recycled. Failures on the acquire path are rethrown after
114  * the object has been put back on the free list.
115  */
116  enum Policy : uint8_t {
117  kPolicyNone = 0, ///< Never invoke the reset callback.
118  kPolicyRelease = 1, ///< Reset on the return-to-pool path.
119  kPolicyAcquire = 2, ///< Reset on the hand-out-to-caller path.
120  kPolicyBoth = 3, ///< Reset on both paths.
121  };
122 
123  /**
124  * @struct Stats
125  * @brief Point-in-time snapshot of internal pool counters.
126  */
127  struct Stats final {
128  size_t pool_size{0}; ///< Objects currently idle on the free list.
129  size_t borrowed{0}; ///< Objects currently held by callers.
130  size_t total_created{0}; ///< Cumulative objects ever produced by the factory.
131  size_t max_size{0}; ///< Configured upper bound; @c 0 means unlimited.
132  };
133 
134  /**
135  * @brief Returns the number of objects currently checked out of the pool.
136  *
137  * @return Borrowed object count.
138  */
139  [[nodiscard]] size_t borrowed() const;
140 
141  /**
142  * @brief Returns the cumulative number of objects ever produced by the factory.
143  *
144  * @return Total created count.
145  */
146  [[nodiscard]] size_t total_created() const;
147 
148  /**
149  * @brief Returns the configured upper bound on live objects.
150  *
151  * @return Maximum size; @c 0 means unlimited.
152  */
153  [[nodiscard]] size_t max_size() const noexcept;
154 
155  protected:
156  ObjectPoolBase(size_t max_size, size_t initial_size, Policy policy);
157 
158  ~ObjectPoolBase() noexcept = default;
159 
160  void safe_dec_borrowed_and_live() noexcept;
161 
162  [[nodiscard]] bool should_reset_on_acquire() const noexcept;
163 
164  [[nodiscard]] bool should_reset_on_release() const noexcept;
165 
166  [[noreturn]] static void throw_invalid_size();
167 
168  [[noreturn]] static void throw_factory_null_pre_fill();
169 
170  [[noreturn]] static void throw_factory_null();
171 
172  [[noreturn]] void throw_exhausted(size_t pool_size) const;
173 
174  Policy policy_{kPolicyNone};
175  size_t max_size_{0};
176 
177  mutable std::mutex mutex_;
178 
179  size_t borrowed_{0};
180  size_t live_count_{0};
181  size_t total_created_{0};
182 };
183 
184 /**
185  * @class ObjectPool
186  * @brief Thread-safe recycling pool for instances of @p T with RAII acquisition.
187  *
188  * @details
189  * Must always live behind a @c std::shared_ptr because @c PoolDeleter holds a
190  * @c std::weak_ptr back to the owning pool; constructing one on the stack leaks
191  * outstanding RAII handles into a dangling pool. Use @c std::make_shared<ObjectPool<T>>().
192  *
193  * @tparam T Pooled element type. Must be default-constructible unless a non-default
194  * factory callback is supplied.
195  */
196 template <typename T>
197 class ObjectPool : public ObjectPoolBase, public std::enable_shared_from_this<ObjectPool<T>> {
198  public:
199  /**
200  * @brief Signature of the factory used to grow the pool on demand.
201  *
202  * @details
203  * A non-null @c std::unique_ptr<T> must be returned; @c nullptr or thrown exceptions
204  * are propagated to the caller of @c get / @c get_shared / @c borrow.
205  */
207 
208  /**
209  * @brief Signature of the reset hook used to scrub objects on acquire and/or release.
210  *
211  * @details
212  * Exceptions thrown on the release path are swallowed and the affected object is
213  * discarded. Exceptions on the acquire path return the object to the pool and
214  * rethrow to the caller.
215  */
216  using ResetCallback = MoveFunction<void(T&)>;
217 
218  /**
219  * @struct PoolDeleter
220  * @brief Custom deleter installed on every RAII handle handed out by @c get / @c get_shared.
221  *
222  * @details
223  * Holds a non-owning @c weak_ptr to the parent pool. When the handle is destroyed,
224  * the deleter either returns the object via @c release() (when the pool is alive)
225  * or falls back to @c operator @c delete (when the pool has already been torn down).
226  */
227  struct PoolDeleter final {
228  /**
229  * @brief Returns @p ptr to the pool, or deletes it when the pool is gone.
230  *
231  * @param ptr Raw pointer to the object being released; @c nullptr is silently ignored.
232  */
233  void operator()(T* ptr) const noexcept;
234  std::weak_ptr<ObjectPool<T>> weak_pool; ///< Weak reference to the parent pool.
235  };
236 
237  /**
238  * @brief Constructs the pool and optionally pre-warms the free list.
239  *
240  * @param factory_callback Factory used to grow the pool. Default: @c std::make_unique<T>().
241  * @param initial_size Objects to allocate eagerly at construction time.
242  * @param max_size Cap on the number of live objects. @c 0 means unlimited.
243  * @param reset_callback Optional scrubbing hook driven by @p policy.
244  * @param policy When the reset hook fires. Default: @c kPolicyRelease.
245  *
246  * @throws std::invalid_argument when @p initial_size exceeds a non-zero @p max_size.
247  * @throws std::runtime_error when the factory returns @c nullptr while pre-filling.
248  *
249  * @note Always construct via @c std::make_shared<ObjectPool<T>>() so that
250  * @c PoolDeleter can resolve its @c weak_ptr.
251  */
252  explicit ObjectPool(FactoryCallback factory_callback = get_default_factory(), size_t initial_size = 0,
253  size_t max_size = 0, ResetCallback reset_callback = nullptr, Policy policy = kPolicyRelease);
254 
255  /**
256  * @brief Acquires an object wrapped in a @c unique_ptr with automatic return.
257  *
258  * @return RAII handle whose deleter releases the object back to the pool.
259  *
260  * @throws std::runtime_error when the pool is exhausted or the factory fails.
261  */
262  [[nodiscard]] std::unique_ptr<T, typename ObjectPool<T>::PoolDeleter> get();
263 
264  /**
265  * @brief Acquires an object wrapped in a @c shared_ptr with automatic return.
266  *
267  * @return RAII handle whose deleter releases the object back to the pool.
268  *
269  * @throws std::runtime_error when the pool is exhausted or the factory fails.
270  */
271  [[nodiscard]] std::shared_ptr<T> get_shared();
272 
273  /**
274  * @brief Acquires an object as a raw pointer; the caller owns the return path.
275  *
276  * @return Raw pointer that must later be returned via @c give_back().
277  *
278  * @throws std::runtime_error when the pool is exhausted or the factory fails.
279  */
280  [[nodiscard]] T* borrow();
281 
282  /**
283  * @brief Returns an object obtained from @c borrow() back to the pool.
284  *
285  * @param ptr Raw pointer previously returned by @c borrow(); @c nullptr is ignored.
286  */
287  void give_back(T* ptr);
288 
289  /**
290  * @brief Captures a thread-safe snapshot of pool statistics.
291  *
292  * @return Filled @c Stats value.
293  */
294  [[nodiscard]] Stats stats() const;
295 
296  /**
297  * @brief Returns the number of objects currently idle on the free list.
298  *
299  * @return Idle object count.
300  */
301  [[nodiscard]] size_t size() const;
302 
303  private:
304  static FactoryCallback get_default_factory();
305 
306  std::unique_ptr<T> acquire();
307 
308  void release(std::unique_ptr<T> obj) noexcept;
309 
310  FactoryCallback factory_callback_;
311  ResetCallback reset_callback_;
312  std::vector<std::unique_ptr<T>> pool_;
313 
315 };
316 
317 ////////////////////////////////////////////////////////////////
318 /// Details
319 ////////////////////////////////////////////////////////////////
320 
321 template <typename T>
322 inline ObjectPool<T>::ObjectPool(FactoryCallback factory_callback, size_t initial_size, size_t max_size,
323  ResetCallback reset_callback, Policy policy)
324  : ObjectPoolBase(max_size, initial_size, policy),
325  factory_callback_(std::move(factory_callback)),
326  reset_callback_(std::move(reset_callback)) {
327  pool_.reserve(initial_size);
328  for (size_t i = 0; i < initial_size; ++i) {
329  auto obj = factory_callback_();
330 
331  if VUNLIKELY (!obj) {
333  }
334 
335  if (should_reset_on_release() && reset_callback_) {
336  reset_callback_(*obj);
337  }
338 
339  pool_.emplace_back(std::move(obj));
340  }
341 
342  total_created_ = initial_size;
343  live_count_ = initial_size;
344 }
345 
346 template <typename T>
347 inline std::unique_ptr<T, typename ObjectPool<T>::PoolDeleter> ObjectPool<T>::get() {
348  std::unique_ptr<T> obj = acquire();
349 
350  try {
351  if (should_reset_on_acquire() && reset_callback_) {
352  reset_callback_(*obj);
353  }
354  } catch (...) { // LCOV_EXCL_LINE GCOVR_EXCL_LINE
355  release(std::move(obj)); // LCOV_EXCL_LINE GCOVR_EXCL_LINE
356  throw; // LCOV_EXCL_LINE GCOVR_EXCL_LINE
357  }
358 
359  return {obj.release(), PoolDeleter{this->weak_from_this()}};
360 }
361 
362 template <typename T>
363 inline std::shared_ptr<T> ObjectPool<T>::get_shared() {
364  std::unique_ptr<T> obj = acquire();
365 
366  try {
367  if (should_reset_on_acquire() && reset_callback_) {
368  reset_callback_(*obj);
369  }
370  } catch (...) { // LCOV_EXCL_LINE GCOVR_EXCL_LINE
371  release(std::move(obj)); // LCOV_EXCL_LINE GCOVR_EXCL_LINE
372  throw; // LCOV_EXCL_LINE GCOVR_EXCL_LINE
373  }
374 
375  return {obj.release(), PoolDeleter{this->weak_from_this()}};
376 }
377 
378 template <typename T>
380  std::unique_ptr<T> obj = acquire();
381 
382  try {
383  if (should_reset_on_acquire() && reset_callback_) {
384  reset_callback_(*obj);
385  }
386  } catch (...) { // LCOV_EXCL_LINE GCOVR_EXCL_LINE
387  release(std::move(obj)); // LCOV_EXCL_LINE GCOVR_EXCL_LINE
388  throw; // LCOV_EXCL_LINE GCOVR_EXCL_LINE
389  }
390 
391  return obj.release();
392 }
393 
394 template <typename T>
395 inline void ObjectPool<T>::give_back(T* ptr) {
396  if VUNLIKELY (!ptr) {
397  return;
398  }
399 
400  std::unique_ptr<T> u(ptr);
401  release(std::move(u));
402 }
403 
404 template <typename T>
406  std::lock_guard lock(mutex_);
407  return {pool_.size(), borrowed_, total_created_, max_size_};
408 }
409 
410 template <typename T>
411 inline size_t ObjectPool<T>::size() const {
412  std::lock_guard lock(mutex_);
413  return pool_.size();
414 }
415 
416 template <typename T>
418  return [] { return std::make_unique<T>(); };
419 }
420 
421 template <typename T>
422 inline std::unique_ptr<T> ObjectPool<T>::acquire() {
423  std::unique_lock lock(mutex_);
424 
425  if VLIKELY (!pool_.empty()) {
426  std::unique_ptr<T> obj = std::move(pool_.back());
427  pool_.pop_back();
428  ++borrowed_;
429  return obj;
430  } // LCOV_EXCL_LINE GCOVR_EXCL_LINE
431 
432  if VUNLIKELY (max_size_ > 0 && live_count_ >= max_size_) {
433  throw_exhausted(pool_.size());
434  }
435 
436  ++live_count_;
437  ++total_created_;
438  ++borrowed_;
439 
440  lock.unlock();
441 
442  std::unique_ptr<T> new_obj;
443  try {
444  new_obj = factory_callback_();
445 
446  if VUNLIKELY (!new_obj) {
447  throw_factory_null();
448  }
449  } catch (...) {
450  lock.lock();
451  --borrowed_;
452  --live_count_;
453  --total_created_;
454  throw;
455  }
456 
457  return new_obj;
458 }
459 
460 template <typename T>
461 inline void ObjectPool<T>::release(std::unique_ptr<T> obj) noexcept {
462  if VUNLIKELY (!obj) {
463  return; // LCOV_EXCL_LINE GCOVR_EXCL_LINE
464  }
465 
466  if (should_reset_on_release() && reset_callback_) {
467  try {
468  reset_callback_(*obj);
469  } catch (...) {
470  safe_dec_borrowed_and_live();
471  return;
472  }
473  }
474 
475  {
476  std::lock_guard lock(mutex_);
477 
478  if VLIKELY (borrowed_ > 0) {
479  --borrowed_;
480  }
481 
482  try {
483  pool_.emplace_back(std::move(obj));
484  } catch (...) { // LCOV_EXCL_LINE GCOVR_EXCL_LINE
485  if (live_count_ > 0) { // LCOV_EXCL_LINE GCOVR_EXCL_LINE
486  --live_count_; // LCOV_EXCL_LINE GCOVR_EXCL_LINE
487  }
488  }
489  }
490 }
491 
492 template <typename T>
493 inline void ObjectPool<T>::PoolDeleter::operator()(T* ptr) const noexcept {
494  if VUNLIKELY (!ptr) {
495  return; // LCOV_EXCL_LINE GCOVR_EXCL_LINE
496  }
497 
498  if (auto sp = weak_pool.lock()) {
499  std::unique_ptr<T> u(ptr);
500  sp->release(std::move(u));
501  } else {
502  delete ptr;
503  }
504 }
505 
506 } // namespace vlink
Pool-backed type-erased callables: copyable vlink::Function and move-only vlink::MoveFunction.
Cross-platform macros for visibility, branch hints, copy prevention, singletons and string helpers.
#define VUNLIKELY(...)
Short alias for VLINK_UNLIKELY.
Definition: macros.h:289
#define VLINK_EXPORT
Definition: macros.h:81
#define VLIKELY(...)
Short alias for VLINK_LIKELY.
Definition: macros.h:284
#define VLINK_DISALLOW_COPY_AND_ASSIGN(classname)
Deletes the copy constructor and copy-assignment operator of classname.
Definition: macros.h:174