Actual source code: objpool.hpp
1: #ifndef PETSCOBJECTPOOL_HPP
2: #define PETSCOBJECTPOOL_HPP
4: #include <petscsys.h>
6: #if defined(__cplusplus)
8: #include <stack>
9: #include <type_traits>
11: namespace Petsc
12: {
14: // Allocator ABC for interoperability with C ctors and dtors.
15: template <typename T>
16: class AllocatorBase
17: {
18: public:
19: using value_type = T;
21: PETSC_NODISCARD PetscErrorCode create(value_type*) noexcept;
22: PETSC_NODISCARD PetscErrorCode destroy(value_type&) noexcept;
23: PETSC_NODISCARD PetscErrorCode reset(value_type&) noexcept;
24: PETSC_NODISCARD PetscErrorCode finalize() noexcept;
26: protected:
27: // make the constructor protected, this forces this class to be derived from to ever be
28: // instantiated
29: AllocatorBase() noexcept = default;
30: };
32: // Default allocator that performs the bare minimum of petsc object creation and
33: // desctruction
34: template <typename T>
35: class CAllocator : public AllocatorBase<T>
36: {
37: public:
38: using allocator_type = AllocatorBase<T>;
39: using value_type = typename allocator_type::value_type;
41: PETSC_NODISCARD PetscErrorCode create(value_type *obj) const noexcept
42: {
43: PetscNew(obj);
44: return 0;
45: }
47: PETSC_NODISCARD PetscErrorCode destroy(value_type &obj) const noexcept
48: {
49: (*obj->ops->destroy)(obj);
50: PetscHeaderDestroy(&obj);
51: return 0;
52: }
54: PETSC_NODISCARD PetscErrorCode reset(value_type &obj) const noexcept
55: {
56: this->destroy(obj);
57: this->create(&obj);
58: return 0;
59: }
61: PETSC_NODISCARD PetscErrorCode finalize() const noexcept { return 0; }
62: };
64: namespace detail
65: {
67: // Base class to object pool, defines helpful typedefs and stores the allocator instance
68: template <typename T, class Allocator>
69: class ObjectPoolBase
70: {
71: public:
72: using allocator_type = Allocator;
73: using value_type = typename allocator_type::value_type;
75: protected:
76: allocator_type alloc_;
78: PETSC_NODISCARD allocator_type& allocator() noexcept { return alloc_; }
79: PETSC_NODISCARD const allocator_type& callocator() const noexcept { return alloc_; }
81: // default constructor
82: constexpr ObjectPoolBase() noexcept(std::is_nothrow_default_constructible<allocator_type>::value)
83: : alloc_()
84: { }
86: // const copy constructor
87: explicit ObjectPoolBase(const allocator_type &alloc) : alloc_(alloc) { }
89: // move constructor
90: explicit ObjectPoolBase(allocator_type &&alloc)
91: noexcept(std::is_nothrow_move_assignable<allocator_type>::value)
92: : alloc_(std::move(alloc))
93: { }
95: static_assert(std::is_base_of<AllocatorBase<value_type>,Allocator>::value,"");
96: };
98: } // namespace detail
100: // default implementation, use the petsc c allocator
101: template <typename T, class Allocator = CAllocator<T>> class ObjectPool;
103: // multi-purpose basic object-pool, useful for recirculating old "destroyed" objects. Uses
104: // a stack to take advantage of LIFO for memory locallity. Registers all objects to be
105: // cleaned up on PetscFinalize()
106: template <typename T, class Allocator>
107: class ObjectPool : detail::ObjectPoolBase<T,Allocator>
108: {
109: protected:
110: using base_type = detail::ObjectPoolBase<T,Allocator>;
112: public:
113: using allocator_type = typename base_type::allocator_type;
114: using value_type = typename base_type::value_type;
115: using stack_type = std::stack<value_type>;
116: using base_type::allocator;
117: using base_type::callocator;
119: private:
120: stack_type stack_;
121: bool registered_ = false;
123: PETSC_NODISCARD PetscErrorCode registerFinalize_() noexcept;
124: PETSC_NODISCARD PetscErrorCode finalizer_() noexcept;
125: PETSC_NODISCARD static PetscErrorCode staticFinalizer_(void*) noexcept;
127: public:
128: // default constructor
129: constexpr ObjectPool() noexcept(std::is_nothrow_default_constructible<allocator_type>::value)
130: : stack_()
131: { }
133: // destructor
134: ~ObjectPool() noexcept
135: {
136: PETSC_COMM_SELF,finalizer_();
137: }
139: // copy constructor
140: ObjectPool(ObjectPool &other) noexcept(std::is_nothrow_copy_constructible<stack_type>::value)
141: : stack_(other.stack_),registered_(other.registered_)
142: { }
144: // const copy constructor
145: ObjectPool(const ObjectPool &other)
146: noexcept(std::is_nothrow_copy_constructible<stack_type>::value)
147: : stack_(other.stack_),registered_(other.registered_)
148: { }
150: // move constructor
151: ObjectPool(ObjectPool &&other) noexcept(std::is_nothrow_move_constructible<stack_type>::value)
152: : stack_(std::move(other.stack_)),registered_(std::move(other.registered_))
153: { }
155: // copy constructor with allocator
156: explicit ObjectPool(const allocator_type &alloc) : base_type(alloc) { }
158: // move constructor with allocator
159: explicit ObjectPool(allocator_type &&alloc)
160: noexcept(std::is_nothrow_move_constructible<allocator_type>::value)
161: : base_type(std::move(alloc))
162: { }
164: // Retrieve an object from the pool, if the pool is empty a new object is created instead
165: PETSC_NODISCARD PetscErrorCode get(value_type&) noexcept;
167: // Return an object to the pool, the object need not necessarily have been created by
168: // the pool, note this only accepts r-value references. The pool takes ownership of all
169: // managed objects.
170: PETSC_NODISCARD PetscErrorCode reclaim(value_type&&) noexcept;
172: // operators
173: template <typename T_, class A_>
174: PetscBool friend operator==(const ObjectPool<T_,A_>&,const ObjectPool<T_,A_>&) noexcept;
176: template <typename T_, class A_>
177: PetscBool friend operator< (const ObjectPool<T_,A_>&,const ObjectPool<T_,A_>&) noexcept;
178: };
180: template <typename T, class Allocator>
181: inline PetscBool operator==(const ObjectPool<T,Allocator> &l,const ObjectPool<T,Allocator> &r) noexcept
182: {
183: return static_cast<PetscBool>(l.stack_ == r.stack_);
184: }
186: template <typename T, class Allocator>
187: inline PetscBool operator< (const ObjectPool<T,Allocator> &l, const ObjectPool<T,Allocator> &r) noexcept
188: {
189: return static_cast<PetscBool>(l.stack_ < r.stack_);
190: }
192: template <typename T, class Allocator>
193: inline PetscBool operator!=(const ObjectPool<T,Allocator> &l, const ObjectPool<T,Allocator> &r) noexcept
194: {
195: return !(l.stack_ == r.stack_);
196: }
198: template <typename T, class Allocator>
199: inline PetscBool operator> (const ObjectPool<T,Allocator> &l, const ObjectPool<T,Allocator> &r) noexcept
200: {
201: return r.stack_ < l.stack_;
202: }
204: template <typename T, class Allocator>
205: inline PetscBool operator>=(const ObjectPool<T,Allocator> &l, const ObjectPool<T,Allocator> &r) noexcept
206: {
207: return !(l.stack_ < r.stack_);
208: }
210: template <typename T, class Allocator>
211: inline PetscBool operator<=(const ObjectPool<T,Allocator> &l, const ObjectPool<T,Allocator> &r) noexcept
212: {
213: return !(r.stack_ < l.stack_);
214: }
216: template <typename T, class Allocator>
217: inline PetscErrorCode ObjectPool<T,Allocator>::finalizer_() noexcept
218: {
219: while (!stack_.empty()) {
220: // we do CHKERRQ __after__ the CHKERCXX on the off chance that someone uses the CXX
221: // error handler, we don't want to catch our own exception!
222: PetscCall(this->allocator().destroy(stack_.top()));
223: stack_.pop();
224: }
225: this->allocator().finalize();
226: registered_ = false;
227: return 0;
228: }
230: template <typename T, class Allocator>
231: inline PetscErrorCode ObjectPool<T,Allocator>::staticFinalizer_(void *obj) noexcept
232: {
233: static_cast<ObjectPool<T,Allocator>*>(obj)->finalizer_();
234: return 0;
235: }
237: template <typename T, class Allocator>
238: inline PetscErrorCode ObjectPool<T,Allocator>::registerFinalize_() noexcept
239: {
240: PetscContainer contain;
242: if (PetscLikely(registered_)) return 0;
243: /* use a PetscContainer as a form of thunk, it holds not only a pointer to this but
244: also the pointer to the static member function, which just converts the thunk back
245: to this. none of this would be needed if PetscRegisterFinalize() just took a void*
246: itself though... */
247: PetscContainerCreate(PETSC_COMM_SELF,&contain);
248: PetscContainerSetPointer(contain,this);
249: PetscContainerSetUserDestroy(contain,staticFinalizer_);
250: PetscObjectRegisterDestroy(reinterpret_cast<PetscObject>(contain));
251: registered_ = true;
252: return 0;
253: }
255: template <typename T, class Allocator>
256: inline PetscErrorCode ObjectPool<T,Allocator>::get(value_type &obj) noexcept
257: {
258: registerFinalize_();
259: if (stack_.empty()) {
260: this->allocator().create(&obj);
261: } else {
262: obj = std::move(stack_.top());
263: stack_.pop();
264: }
265: return 0;
266: }
268: template <typename T, class Allocator>
269: inline PetscErrorCode ObjectPool<T,Allocator>::reclaim(value_type &&obj) noexcept
270: {
271: if (PetscLikely(registered_)) {
272: // allows const allocator_t& to be used if allocator defines a const reset
273: this->allocator().reset(obj);
274: stack_.push(std::move(obj));
275: } else {
276: // this is necessary if an object is "reclaimed" within another PetscFinalize() registered
277: // cleanup after this object pool has returned from it's finalizer. In this case, instead
278: // of pushing onto the stack we just destroy the object directly
279: this->allocator().destroy(std::move(obj));
280: }
281: return 0;
282: }
284: } // namespace Petsc
286: #endif /* __cplusplus */
288: #endif /* PETSCOBJECTPOOL_HPP */