A unique_ptr pool

published at 22.06.2017 18:25 by Jens Weller

A few weeks ago I wrote about a self-contained unique_ptr pool, which today I refactored into a more reuseable template. Reason is, that the pool class it self now needs to maintain two different kind of objects into their pools, hence the actual pool code is best now factored out into a template.

The last two episodes of just C++ are about this pool class.

The pool still works on the same mechanism, it holds its objects in a unique_ptr and also returns a unique_ptr to the caller. But, the unique_ptr the caller gets has a custom deleter returning the object to the free list of the pool. Like previous, the free list is just part of the vector holding all objects for the pool.

The pool has one type parameter for the pooled Type, and the previous variables for holding data, the begin of the free list and the mutex:

template<class Pooled> struct PoolService { using del = std::function<void(const Pooled*)>; using rt_ptr = std::unique_ptr<Pooled,del>; private: using ptr = std::unique_ptr<Pooled>; using pool = std::vector<ptr>; pool mypool; typename pool::iterator free = mypool.end(); std::mutex m;

The type definition of rt_ptr needs to be public, so that the refactored class can return this from its interface. PoolService has only one member function, receivePtr, which is templated for the initialization lambda, as dlib likes to call deserialize on newly created objects, I wanted to have this code on the callers site:

template rt_ptr receivePtr(const Init& init) { auto del = [this](const Pooled* obj){ std::lock_guard< std::mutex > lg(m); auto it = std::find_if(mypool.begin(),free,[obj](const ptr& u){return u.get() == obj;}); if(it != mypool.end() && free != mypool.begin() && it != --free) std::iter_swap(it,free); }; std::lock_guard<std::mutex> lg(m); if(free == mypool.end()) { mypool.emplace_back(std::make_unique()); auto p = mypool.rbegin()->get(); init(p); free = mypool.end(); return rt_ptr{p,del}; } auto p = free->get(); free++; return rt_ptr{p,del}; }

In the previous version there was still a method for freeing the allocated object in the pool, this is now done by a lambda inside the method for obtaining an object from the pool. As only unique_ptr with a custom deleter are ever returned to the user, there is no need to expose this to the user. The init method could be removed or treated differently, but currently all the calling code needs some way to initialize a newly created object. Thats why currently it is not needed to have a variadic template version of this method, which forwards its arguments to make_unique. That is left as an excerise to the reader...

On the application side, the pool class is now much cleaner, then it was previously:

class FaceDetectionPool { std::istringstream in,sp_in; std::future< std::string > spreader; using ffd_pool = PoolService< dlib::frontal_face_detector >; ffd_pool ffdpool; using sp_pool = PoolService< dlib::shape_predictor >; sp_pool sppool; public: FaceDetectionPool(); ffd_pool::rt_ptr getFacedetector(){ static auto init = [this](dlib::frontal_face_detector* ffd){ dlib::deserialize(*ffd,in); in.seekg(0); }; return ffdpool.receivePtr(init); } sp_pool::rt_ptr getShapePredictor(){ static auto init = [this](dlib::shape_predictor* sp){ if(sp_in.str().size() ==0) sp_in.str(spreader.get()); dlib::deserialize(*sp,sp_in); sp_in.seekg(0); }; return sppool.receivePtr(init); } };

The current pool template reflects my needs in loading objects from dlib, when other use cases arise, it will need likely a refactoring to reflect these.

Join the Meeting C++ patreon community!

This and other posts on Meeting C++ are enabled by my supporters on patreon!