Actual source code: objpool.hpp

  1: #ifndef PETSCOBJECTPOOL_HPP
  2: #define PETSCOBJECTPOOL_HPP

  4: #include <petscsys.h>
  5: #if !PetscDefined(HAVE_CXX_DIALECT_CXX11)
  6: #error "ObjectPool requires c++11"
  7: #endif

  9: #if defined(__cplusplus)

 11: #include <stack>
 12: #include <type_traits>

 14: #define PETSC_STATIC_ASSERT_BASE_CLASS(base_,derived_,mess_) \
 15:   static_assert(std::is_base_of<base_,derived_>::value,mess_)

 17: namespace Petsc {

 19: // Allocator ABC for interoperability with C ctors and dtors.
 20: template <typename T>
 21: class Allocator {
 22: public:
 23:   typedef T value_type;

 25:   PETSC_NODISCARD PetscErrorCode create(value_type*)  noexcept;
 26:   PETSC_NODISCARD PetscErrorCode destroy(value_type&) noexcept;
 27:   PETSC_NODISCARD PetscErrorCode reset(value_type&)   noexcept;
 28:   PETSC_NODISCARD PetscErrorCode finalize(void)       noexcept;

 30: protected:
 31:   // make the constructor protected, this forces this class to be derived from to ever be
 32:   // instantiated
 33:   Allocator() {}
 34:   ~Allocator() {}
 35: };

 37: // Default allocator that performs the bare minimum of petsc object creation and
 38: // desctruction
 39: template <typename T>
 40: class CAllocator : Allocator<T>
 41: {
 42: public:
 43:   typedef Allocator<T>                     allocator_t;
 44:   typedef typename allocator_t::value_type value_type;

 46:   PETSC_NODISCARD PetscErrorCode create(value_type *obj) const noexcept
 47:   {

 51:     PetscNew(obj);
 52:     return(0);
 53:   }

 55:   PETSC_NODISCARD PetscErrorCode destroy(value_type &obj) const noexcept
 56:   {

 60:     (*obj->ops->destroy)(obj);
 61:     PetscHeaderDestroy(&obj);
 62:     return(0);
 63:   }

 65:   PETSC_NODISCARD PetscErrorCode reset(value_type &obj) const noexcept
 66:   {

 70:     this->destroy(obj);
 71:     this->create(&obj);
 72:     return(0);
 73:   }

 75:   PETSC_NODISCARD PetscErrorCode finalize(void) const noexcept { return 0;}
 76: };

 78: // Base class to object pool, defines helpful typedefs and stores the allocator instance
 79: template <typename T, class _Allocator>
 80: class ObjectPoolBase {
 81: public:
 82:   typedef _Allocator                       allocator_t;
 83:   typedef typename allocator_t::value_type value_type;

 85: protected:
 86:   allocator_t _alloc;

 88:   PETSC_NODISCARD allocator_t& __getAllocator() noexcept { return this->_alloc;}

 90:   PETSC_NODISCARD const allocator_t& __getAllocator() const noexcept { return this->_alloc;}

 92:   // default constructor
 93:   constexpr ObjectPoolBase() noexcept(std::is_nothrow_default_constructible<allocator_t>::value) : _alloc() {}

 95:   // const copy constructor
 96:   explicit ObjectPoolBase(const allocator_t &alloc) : _alloc{alloc} {}

 98:   // move constructor
 99:   explicit ObjectPoolBase(allocator_t &&alloc) noexcept(std::is_nothrow_move_assignable<allocator_t>::value) : _alloc{std::move(alloc)} {}

101:   ~ObjectPoolBase()
102:   {
103:     PETSC_STATIC_ASSERT_BASE_CLASS(Allocator<value_type>,_Allocator,"Allocator type must be subclass of Petsc::Allocator");
104:   }
105: };

107: // default implementation, use the petsc c allocator
108: template <typename T, class _Allocator = CAllocator<T>> class ObjectPool;

110: // multi-purpose basic object-pool, useful for recirculating old "destroyed" objects. Uses
111: // a stack to take advantage of LIFO for memory locallity. Registers all objects to be
112: // cleaned up on PetscFinalize()
113: template <typename T, class _Allocator>
114: class ObjectPool : ObjectPoolBase<T,_Allocator> {
115: protected:
116:   typedef ObjectPoolBase<T,_Allocator> base_t;

118: public:
119:   typedef typename base_t::allocator_t allocator_t;
120:   typedef typename base_t::value_type  value_type;
121:   typedef std::stack<value_type>       stack_type;

123: protected:
124:   stack_type _stack;
125:   PetscBool  _registered{PETSC_FALSE};

127: private:
128:   PETSC_NODISCARD static PetscErrorCode __staticFinalizer(void*) noexcept;
129:   PETSC_NODISCARD PetscErrorCode __finalizer(void) noexcept;
130:   PETSC_NODISCARD PetscErrorCode __registerFinalize(void) noexcept;

132: public:
133:   // default constructor
134:   constexpr ObjectPool() noexcept(std::is_nothrow_default_constructible<allocator_t>::value) {}

136:   // copy constructor
137:   ObjectPool(ObjectPool &other) noexcept(std::is_nothrow_copy_constructible<stack_type>::value) : _stack{other._stack},_registered{other._registered} {}

139:   // const copy constructor
140:   ObjectPool(const ObjectPool &other) noexcept(std::is_nothrow_copy_constructible<stack_type>::value) : _stack{other._stack},_registered{other._registered} {}

142:   // move constructor
143:   ObjectPool(ObjectPool &&other) noexcept(std::is_nothrow_move_constructible<stack_type>::value) : _stack{std::move(other._stack)},_registered{std::move(other._registered)} {}

145:   // copy constructor with allocator
146:   explicit ObjectPool(const allocator_t &alloc) : base_t{alloc},_registered{PETSC_FALSE} {}

148:   // move constructor with allocator
149:   explicit ObjectPool(allocator_t &&alloc) noexcept(std::is_nothrow_move_constructible<allocator_t>::value) : base_t{std::move(alloc)},_registered{PETSC_FALSE} {}

151:   // Retrieve an object from the pool, if the pool is empty a new object is created instead
152:   PETSC_NODISCARD PetscErrorCode get(value_type&)      noexcept;
153:   // Return an object to the pool, the object need not necessarily have been created by
154:   // the pool
155:   PETSC_NODISCARD PetscErrorCode reclaim(value_type&)  noexcept;
156:   PETSC_NODISCARD PetscErrorCode reclaim(value_type&&) noexcept;

158:   // operators
159:   template <typename T_, class A_>
160:   PetscBool friend operator==(const ObjectPool<T_,A_>&,const ObjectPool<T_,A_>&);

162:   template <typename T_, class A_>
163:   PetscBool friend operator< (const ObjectPool<T_,A_>&,const ObjectPool<T_,A_>&);
164: };

166: template <typename T, class _Allocator>
167: inline PetscBool operator==(const ObjectPool<T,_Allocator> &l,const ObjectPool<T,_Allocator> &r)
168: {
169:   return static_cast<PetscBool>(l._stack == r._stack);
170: }

172: template <typename T, class _Allocator>
173: inline PetscBool operator< (const ObjectPool<T,_Allocator> &l, const ObjectPool<T,_Allocator> &r)
174: {
175:   return static_cast<PetscBool>(l._stack < r._stack);
176: }

178: template <typename T, class _Allocator>
179: inline PetscBool operator!=(const ObjectPool<T,_Allocator> &l, const ObjectPool<T,_Allocator> &r)
180: {
181:   return !(l._stack == r._stack);
182: }

184: template <typename T, class _Allocator>
185: inline PetscBool operator> (const ObjectPool<T,_Allocator> &l, const ObjectPool<T,_Allocator> &r)
186: {
187:   return r._stack < l._stack;
188: }

190: template <typename T, class _Allocator>
191: inline PetscBool operator>=(const ObjectPool<T,_Allocator> &l, const ObjectPool<T,_Allocator> &r)
192: {
193:   return !(l._stack < r._stack);
194: }

196: template <typename T, class _Allocator>
197: inline PetscBool operator<=(const ObjectPool<T,_Allocator> &l, const ObjectPool<T,_Allocator> &r)
198: {
199:   return !(r._stack < l._stack);
200: }

202: template <typename T, class _Allocator>
203: PetscErrorCode ObjectPool<T,_Allocator>::__staticFinalizer(void *obj) noexcept
204: {

208:   static_cast<ObjectPool<T,_Allocator>*>(obj)->__finalizer();
209:   return(0);
210: }

212: template <typename T, class _Allocator>
213: inline PetscErrorCode ObjectPool<T,_Allocator>::__finalizer(void) noexcept
214: {

218:   while (!this->_stack.empty()) {
219:     // we do CHKERRQ __after__ the CHKERCXX on the off chance that someone uses the CXX
220:     // error handler, we don't want to catch our own exception!
221:     CHKERRCXX(this->__getAllocator().destroy(this->_stack.top()));
222:     CHKERRCXX(this->_stack.pop());
223:   }
224:   this->__getAllocator().finalize();
225:   this->_registered = PETSC_FALSE;
226:   return(0);
227: }

229: template <typename T, class _Allocator>
230: inline PetscErrorCode ObjectPool<T,_Allocator>::__registerFinalize(void) noexcept
231: {
233:   if (PetscUnlikely(!this->_registered)) {
235:     PetscContainer contain;

237:     /* use a PetscContainer as a form of thunk, it holds not only a pointer to this but
238:        also the pointer to the static member function, which just converts the thunk back
239:        to this. none of this would be needed if PetscRegisterFinalize() just took a void*
240:        itself though...  */
241:     PetscContainerCreate(PETSC_COMM_SELF,&contain);
242:     PetscContainerSetPointer(contain,this);
243:     PetscContainerSetUserDestroy(contain,this->__staticFinalizer);
244:     PetscObjectRegisterDestroy((PetscObject)contain);
245:     this->_registered = PETSC_TRUE;
246:   }
247:   return(0);
248: }

250: template <typename T, class _Allocator>
251: inline PetscErrorCode ObjectPool<T,_Allocator>::get(value_type &obj) noexcept
252: {

256:   this->__registerFinalize();
257:   if (this->_stack.empty()) {
258:     this->__getAllocator().create(&obj);
259:   } else {
260:     CHKERRCXX(obj = std::move(this->_stack.top()));
261:     CHKERRCXX(this->_stack.pop());
262:   }
263:   return(0);
264: }

266: template <typename T, class _Allocator>
267: inline PetscErrorCode ObjectPool<T,_Allocator>::reclaim(value_type &obj) noexcept
268: {

272:   if (PetscUnlikely(!this->_registered)) {
273:     /* this is necessary if an object is "reclaimed" within another PetscFinalize()
274:        registered cleanup after this object pool has returned from it's finalizer. In
275:        this case, instead of pushing onto the stack we just destroy the object directly */
276:     this->__getAllocator().destroy(obj);
277:   } else {
278:     this->__getAllocator().reset(obj);
279:     CHKERRCXX(this->_stack.push(std::move(obj)));
280:     obj = nullptr;
281:   }
282:   return(0);
283: }

285: template <typename T, class _Allocator>
286: inline PetscErrorCode ObjectPool<T,_Allocator>::reclaim(value_type &&obj) noexcept
287: {

291:   if (PetscUnlikely(!this->_registered)) {
292:     /* this is necessary if an object is "reclaimed" within another PetscFinalize()
293:        registered cleanup after this object pool has returned from it's finalizer. In
294:        this case, instead of pushing onto the stack we just destroy the object directly */
295:     this->__getAllocator().destroy(obj);
296:   } else {
297:     // allows const allocator_t& to be used if allocator defines a const reset
298:     this->__getAllocator().reset(obj);
299:     CHKERRCXX(this->_stack.push(std::move(obj)));
300:     obj = nullptr;
301:   }
302:   return(0);
303: }

305: } // namespace Petsc

307: #endif /* __cplusplus */

309: #endif /* PETSCOBJECTPOOL_HPP */