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 configurable reset policy and RAII ownership.
27 *
28 * @details
29 * @c ObjectPool<T> maintains a free-list of pre-allocated @c T objects. Callers acquire an
30 * object, use it, then return it to the pool automatically (RAII) or manually. Objects are
31 * recycled rather than destroyed, reducing heap pressure in hot paths.
32 *
33 * Acquisition API:
34 *
35 * | Method | Return type | Auto-return on destruction |
36 * | ------------- | --------------------------------- | --------------------------------- |
37 * | @c get() | unique_ptr<T, PoolDeleter> | Yes (via PoolDeleter) |
38 * | @c get_shared | shared_ptr<T> (PoolDeleter) | Yes (via shared_ptr deleter) |
39 * | @c borrow() | T* (raw pointer) | No -- caller must call give_back() |
40 *
41 * Reset policy controls when the optional @c ResetCallback is invoked:
42 *
43 * | Policy | Reset on acquire | Reset on release | Use case |
44 * | ------------------ | ---------------- | ---------------- | -------------------------------- |
45 * | @c kPolicyNone | No | No | Immutable / stateless objects |
46 * | @c kPolicyRelease | No | Yes | Clean before returning (default) |
47 * | @c kPolicyAcquire | Yes | No | Clean before use |
48 * | @c kPolicyBoth | Yes | Yes | Clean on both sides |
49 *
50 * @par Thread safety
51 * All public methods are protected by an internal mutex and are safe to call concurrently.
52 *
53 * @note
54 * - When @c max_size is 0 (default), the pool grows without bound.
55 * - When the pool is exhausted (@c max_size > 0 and the number of live objects reaches @c max_size),
56 * @c get(), @c get_shared(), and @c borrow() throw @c std::runtime_error.
57 * - If @c FactoryCallback returns @c nullptr, a @c std::runtime_error is thrown.
58 *
59 * @par Example
60 * @code
61 * auto pool = std::make_shared<vlink::ObjectPool<Buffer>>(
62 * []{ return std::make_unique<Buffer>(4096); }, // factory
63 * 4, // initial_size
64 * 16, // max_size
65 * [](Buffer& b){ b.reset(); }, // reset callback
66 * vlink::ObjectPool<Buffer>::kPolicyRelease // reset on return
67 * );
68 *
69 * {
70 * auto buf = pool->get(); // auto-returned when buf goes out of scope
71 * buf->write(data, len);
72 * }
73 *
74 * // Manual acquire/release:
75 * Buffer* raw = pool->borrow();
76 * raw->write(data, len);
77 * pool->give_back(raw);
78 * @endcode
79 *
80 * @tparam T Type of objects managed by the pool.
81 */
82
83#pragma once
84
85#include <functional>
86#include <memory>
87#include <mutex>
88#include <sstream>
89#include <stdexcept>
90#include <utility>
91#include <vector>
92
93#include "./macros.h"
94
95namespace vlink {
96
97/**
98 * @class ObjectPool
99 * @brief Thread-safe object pool for type @p T with RAII acquisition and configurable reset policy.
100 *
101 * @details
102 * Must be heap-allocated and managed via @c std::shared_ptr, because @c PoolDeleter holds a
103 * @c std::weak_ptr to the pool. Create with @c std::make_shared<ObjectPool<T>>(...).
104 *
105 * @tparam T Type of pooled objects. Must be default-constructible unless a custom
106 * @c FactoryCallback is supplied.
107 */
108template <typename T>
109class ObjectPool : public std::enable_shared_from_this<ObjectPool<T>> {
110 public:
111 /**
112 * @brief Callback type for creating a new instance of @p T.
113 *
114 * @details
115 * Must return a non-null @c unique_ptr<T>. Throwing or returning @c nullptr causes
116 * the acquisition call to propagate the error to the caller.
117 */
118 using FactoryCallback = std::function<std::unique_ptr<T>()>;
119
120 /**
121 * @brief Callback type for resetting an object before acquisition or after release.
122 *
123 * @details
124 * Called with a reference to the object. Exceptions thrown here are caught; on release,
125 * the object is discarded (not returned to the pool) if the reset throws.
126 */
127 using ResetCallback = std::function<void(T&)>;
128
129 /**
130 * @brief Controls when the @c ResetCallback is invoked relative to acquire and release.
131 */
132 enum Policy : uint8_t {
133 kPolicyNone = 0, ///< Never invoke reset callback.
134 kPolicyRelease = 1, ///< Invoke reset callback when object is returned to the pool.
135 kPolicyAcquire = 2, ///< Invoke reset callback when object is acquired from the pool.
136 kPolicyBoth = 3, ///< Invoke reset callback on both acquire and release.
137 };
138
139 /**
140 * @brief Snapshot of pool statistics at a point in time.
141 */
142 struct Stats final {
143 size_t pool_size{0}; ///< Number of objects currently idle in the pool.
144 size_t borrowed{0}; ///< Number of objects currently held by callers.
145 size_t total_created{0}; ///< Total objects ever created (pool_size + borrowed + destroyed).
146 size_t max_size{0}; ///< Maximum allowed total objects. 0 means unlimited.
147 };
148
149 /**
150 * @struct PoolDeleter
151 * @brief Custom deleter for RAII handles returned by @c get() and @c get_shared().
152 *
153 * @details
154 * Holds a @c weak_ptr to the parent pool. On destruction of the RAII handle,
155 * @c operator() is called:
156 * - If the pool is still alive, the object is returned to it via @c release().
157 * - If the pool has been destroyed, the object is deleted with @c delete.
158 */
159 struct PoolDeleter final {
160 /**
161 * @brief Returns @p ptr to the pool, or deletes it if the pool is gone.
162 *
163 * @param ptr Raw pointer to the object being released. Ignored if @c nullptr.
164 */
165 void operator()(T* ptr) const noexcept;
166 std::weak_ptr<ObjectPool<T>> weak_pool; ///< Non-owning reference to the parent pool.
167 };
168
169 /**
170 * @brief Constructs the pool and optionally pre-populates it with objects.
171 *
172 * @param factory_callback Factory used to create new @p T instances.
173 * Default: @c std::make_unique<T>().
174 * @param initial_size Number of objects to pre-allocate at construction.
175 * The reset callback (if @c kPolicyRelease or @c kPolicyBoth) is
176 * invoked on each pre-allocated object.
177 * @param max_size Upper bound on total objects (idle + borrowed).
178 * 0 = unlimited.
179 * @param reset_callback Optional callback invoked to reset objects per @p policy.
180 * @param policy When to invoke @p reset_callback (default @c kPolicyRelease).
181 *
182 * @throws std::invalid_argument if @p initial_size > @p max_size and @p max_size > 0.
183 * @throws std::runtime_error if @p factory_callback returns @c nullptr during pre-fill.
184 *
185 * @note Must be used via @c std::make_shared<ObjectPool<T>>(...) to enable @c PoolDeleter.
186 */
187 explicit ObjectPool(FactoryCallback factory_callback = get_default_factory(), size_t initial_size = 0,
188 size_t max_size = 0, ResetCallback reset_callback = nullptr, Policy policy = kPolicyRelease);
189
190 /**
191 * @brief Acquires an object and returns it as a @c unique_ptr with automatic pool return.
192 *
193 * @details
194 * If the pool is non-empty, the most recently returned object is popped (LIFO).
195 * Otherwise a new object is created via the factory callback.
196 * If @c kPolicyAcquire or @c kPolicyBoth is set, the reset callback is applied before
197 * returning the pointer.
198 *
199 * @return A @c unique_ptr<T, PoolDeleter> whose destruction returns the object to the pool.
200 *
201 * @throws std::runtime_error if the pool is exhausted (@c max_size > 0 and all objects are
202 * in use), or if the factory callback returns @c nullptr.
203 */
204 [[nodiscard]] std::unique_ptr<T, typename ObjectPool<T>::PoolDeleter> get();
205
206 /**
207 * @brief Acquires an object and returns it as a @c shared_ptr with automatic pool return.
208 *
209 * @details
210 * Behaves identically to @c get() but returns a @c shared_ptr. The custom deleter is
211 * @c PoolDeleter, so the object is returned to the pool when the last @c shared_ptr copy
212 * is destroyed.
213 *
214 * @return A @c shared_ptr<T> that returns the object to the pool on last-reference destruction.
215 *
216 * @throws std::runtime_error if the pool is exhausted or the factory returns @c nullptr.
217 */
218 [[nodiscard]] std::shared_ptr<T> get_shared();
219
220 /**
221 * @brief Acquires an object and returns a raw pointer; caller is responsible for returning it.
222 *
223 * @details
224 * Unlike @c get() and @c get_shared(), this method does NOT use RAII. The caller
225 * MUST call @c give_back() to return the object. Failure to do so causes a resource leak
226 * and may prevent the pool from reaching @c max_size when needed.
227 *
228 * @return Raw pointer to the acquired object. Never @c nullptr on success.
229 *
230 * @throws std::runtime_error if the pool is exhausted or the factory returns @c nullptr.
231 *
232 * @warning Pair every @c borrow() with a corresponding @c give_back() call.
233 */
234 [[nodiscard]] T* borrow();
235
236 /**
237 * @brief Returns a raw-pointer object previously obtained via @c borrow() to the pool.
238 *
239 * @details
240 * If the reset policy includes @c kPolicyRelease, the reset callback is invoked before
241 * the object re-enters the pool. If the reset callback throws, the object is discarded
242 * rather than returned.
243 *
244 * @param ptr Pointer returned by @c borrow(). Passing @c nullptr is a no-op.
245 *
246 * @note Do NOT call @c give_back() on pointers obtained from @c get() or @c get_shared();
247 * those are managed by their respective deleters.
248 */
249 void give_back(T* ptr);
250
251 /**
252 * @brief Returns a snapshot of all pool statistics (thread-safe).
253 *
254 * @return @c Stats struct containing pool_size, borrowed, total_created, max_size.
255 */
256 [[nodiscard]] Stats stats() const;
257
258 /**
259 * @brief Returns the number of idle objects currently in the pool.
260 *
261 * @return Number of available objects that can be acquired without allocation.
262 */
263 [[nodiscard]] size_t size() const;
264
265 /**
266 * @brief Returns the number of objects currently held by callers.
267 *
268 * @return Count of objects acquired and not yet returned.
269 */
270 [[nodiscard]] size_t borrowed() const;
271
272 /**
273 * @brief Returns the total number of objects ever created by this pool.
274 *
275 * @details
276 * Includes objects currently idle, borrowed, and any that were discarded due to
277 * failed reset callbacks. Never decreases.
278 *
279 * @return Cumulative creation count.
280 */
281 [[nodiscard]] size_t total_created() const;
282
283 /**
284 * @brief Returns the maximum total object count allowed by this pool.
285 *
286 * @return Max size limit. Returns 0 if the pool is unbounded.
287 */
288 [[nodiscard]] size_t max_size() const;
289
290 private:
291 static FactoryCallback get_default_factory();
292
293 std::unique_ptr<T> acquire();
294
295 void release(std::unique_ptr<T> obj) noexcept;
296
297 void safe_dec_borrowed_and_live() noexcept;
298
299 bool should_reset_on_acquire() const noexcept;
300
301 bool should_reset_on_release() const noexcept;
302
303 [[nodiscard]] std::runtime_error exhausted_error_locked() const;
304
305 FactoryCallback factory_callback_;
306 ResetCallback reset_callback_;
307 Policy policy_{kPolicyNone};
308 size_t max_size_{0};
309
310 mutable std::mutex mutex_;
311 std::vector<std::unique_ptr<T>> pool_;
312
313 size_t borrowed_{0};
314 size_t live_count_{0};
315 size_t total_created_{0};
316
318};
319
320////////////////////////////////////////////////////////////////
321/// Details
322////////////////////////////////////////////////////////////////
323
324template <typename T>
325inline ObjectPool<T>::ObjectPool(FactoryCallback factory_callback, size_t initial_size, size_t max_size,
326 ResetCallback reset_callback, Policy policy)
327 : factory_callback_(std::move(factory_callback)),
328 reset_callback_(std::move(reset_callback)),
329 policy_(policy),
330 max_size_(max_size) {
331 if VUNLIKELY (max_size_ > 0 && initial_size > max_size_) {
332 throw std::invalid_argument("initial_size exceeds max_size");
333 }
334
335 pool_.reserve(initial_size);
336 for (size_t i = 0; i < initial_size; ++i) {
337 auto obj = factory_callback_();
338
339 if VUNLIKELY (!obj) {
340 throw std::runtime_error("FactoryCallback returned nullptr during pre-fill");
341 }
342
343 if (should_reset_on_release() && reset_callback_) {
344 reset_callback_(*obj);
345 }
346
347 pool_.emplace_back(std::move(obj));
348 }
349
350 total_created_ = initial_size;
351 live_count_ = initial_size;
352}
353
354template <typename T>
355inline std::unique_ptr<T, typename ObjectPool<T>::PoolDeleter> ObjectPool<T>::get() {
356 std::unique_ptr<T> obj = acquire();
357
358 try {
359 if (should_reset_on_acquire() && reset_callback_) {
360 reset_callback_(*obj);
361 }
362 } catch (...) {
363 release(std::move(obj));
364 throw;
365 }
366
367 return {obj.release(), PoolDeleter{this->weak_from_this()}};
368}
369
370template <typename T>
371inline std::shared_ptr<T> ObjectPool<T>::get_shared() {
372 std::unique_ptr<T> obj = acquire();
373
374 try {
375 if (should_reset_on_acquire() && reset_callback_) {
376 reset_callback_(*obj);
377 }
378 } catch (...) {
379 release(std::move(obj));
380 throw;
381 }
382
383 return {obj.release(), PoolDeleter{this->weak_from_this()}};
384}
385
386template <typename T>
388 std::unique_ptr<T> obj = acquire();
389
390 try {
391 if (should_reset_on_acquire() && reset_callback_) {
392 reset_callback_(*obj);
393 }
394 } catch (...) {
395 release(std::move(obj));
396 throw;
397 }
398
399 return obj.release();
400}
401
402template <typename T>
403inline void ObjectPool<T>::give_back(T* ptr) {
404 if VUNLIKELY (!ptr) {
405 return;
406 }
407
408 std::unique_ptr<T> u(ptr);
409 release(std::move(u));
410}
411
412template <typename T>
414 std::lock_guard<std::mutex> lock(mutex_);
415 return {pool_.size(), borrowed_, total_created_, max_size_};
416}
417
418template <typename T>
419inline size_t ObjectPool<T>::size() const {
420 std::lock_guard<std::mutex> lock(mutex_);
421 return pool_.size();
422}
423
424template <typename T>
425inline size_t ObjectPool<T>::borrowed() const {
426 std::lock_guard<std::mutex> lock(mutex_);
427 return borrowed_;
428}
429
430template <typename T>
431inline size_t ObjectPool<T>::total_created() const {
432 std::lock_guard<std::mutex> lock(mutex_);
433 return total_created_;
434}
435
436template <typename T>
437inline size_t ObjectPool<T>::max_size() const {
438 return max_size_;
439}
440
441template <typename T>
442inline typename ObjectPool<T>::FactoryCallback ObjectPool<T>::get_default_factory() {
443 return [] { return std::make_unique<T>(); };
444}
445
446template <typename T>
447inline std::unique_ptr<T> ObjectPool<T>::acquire() {
448 std::unique_lock<std::mutex> lock(mutex_);
449
450 if (!pool_.empty()) {
451 std::unique_ptr<T> obj = std::move(pool_.back());
452 pool_.pop_back();
453 ++borrowed_;
454 return obj;
455 }
456
457 if VUNLIKELY (max_size_ > 0 && live_count_ >= max_size_) {
458 throw exhausted_error_locked();
459 }
460
461 ++live_count_;
462 ++total_created_;
463 ++borrowed_;
464
465 lock.unlock();
466
467 std::unique_ptr<T> new_obj;
468 try {
469 new_obj = factory_callback_();
470
471 if VUNLIKELY (!new_obj) {
472 throw std::runtime_error("FactoryCallback returned nullptr");
473 }
474 } catch (...) {
475 lock.lock();
476 --borrowed_;
477 --live_count_;
478 --total_created_;
479 throw;
480 }
481
482 return new_obj;
483}
484
485template <typename T>
486inline void ObjectPool<T>::release(std::unique_ptr<T> obj) noexcept {
487 if VUNLIKELY (!obj) {
488 return;
489 }
490
491 if (should_reset_on_release() && reset_callback_) {
492 try {
493 reset_callback_(*obj);
494 } catch (...) {
495 safe_dec_borrowed_and_live();
496 return;
497 }
498 }
499
500 {
501 std::lock_guard<std::mutex> lock(mutex_);
502
503 if (borrowed_ > 0) {
504 --borrowed_;
505 }
506
507 pool_.emplace_back(std::move(obj));
508 }
509}
510
511template <typename T>
512inline void ObjectPool<T>::safe_dec_borrowed_and_live() noexcept {
513 std::lock_guard<std::mutex> lock(mutex_);
514
515 if (borrowed_ > 0) {
516 --borrowed_;
517 }
518
519 if (live_count_ > 0) {
520 --live_count_;
521 }
522}
523
524template <typename T>
525inline bool ObjectPool<T>::should_reset_on_acquire() const noexcept {
526 return policy_ == kPolicyAcquire || policy_ == kPolicyBoth;
527}
528
529template <typename T>
530inline bool ObjectPool<T>::should_reset_on_release() const noexcept {
531 return policy_ == kPolicyRelease || policy_ == kPolicyBoth;
532}
533
534template <typename T>
535inline std::runtime_error ObjectPool<T>::exhausted_error_locked() const {
536 std::ostringstream oss;
537
538 oss << "ObjectPool exhausted: max_size=" << max_size_ << " live_count=" << live_count_
539 << " total_created=" << total_created_ << " borrowed=" << borrowed_ << " pool_size=" << pool_.size();
540
541 return std::runtime_error(oss.str());
542}
543
544template <typename T>
545inline void ObjectPool<T>::PoolDeleter::operator()(T* ptr) const noexcept {
546 if VUNLIKELY (!ptr) {
547 return;
548 }
549
550 if (auto sp = weak_pool.lock()) {
551 std::unique_ptr<T> u(ptr);
552 sp->release(std::move(u));
553 } else {
554 delete ptr;
555 }
556}
557
558} // namespace vlink
Platform-independent macro definitions for the VLink library.
#define VUNLIKELY(...)
Shorthand alias for VLINK_UNLIKELY. Hints that the expression is unlikely true.
定义 macros.h:302
#define VLINK_DISALLOW_COPY_AND_ASSIGN(classname)
Deletes the copy constructor and copy-assignment operator of classname.
定义 macros.h:184
STL namespace