Fork me on GitHub

C++11 Smart Object-Pool with RAII style return to pool

As a fun programming challenge, I wanted to see if it was possible to implement the object pool pattern using smart pointers, such that acquiring objects from the pool follows the RAII principle, and enforced by C++11 smart pointers. When an acquired object is deleted, it automatically returns to the pool it was acquired from.

The basic interface for the pool is as following:

The challenge:

  1. If an acquired objects is deleted, it should return to the pool.
  2. ... if pool itself is already deleted, the object should instead be deallocated.
  3. When the pool is deleted, all objects in the pool should be correctly deallocated.

Example usage:

1. Implementation

2. Explanation

The implementation does a handful "clever" things, making it hard to understand. I wrote it only a few months ago, and I'm scratching my head right now. However, the cleverness in this case is a necessary (and fun) evil.

So, let's break it down. First, look at the how the objects are stored internally:

The T is the type of the objects the pool owns, and D is the deleter with which to destroy the T objects. If no deleter is provided (D), it uses the default for the type T.

The add() function should add objects to this pool, which is simply:

And now, for the hard part
The acquired objects should somehow keep knowledge of where they came from, and have a way of knowing if that place still exists. If the pool is destroyed, there is no place to return to, and they should assume they are in charge of their own destruction.

This smells like a weak_ptr to me, so let's add a shared_ptr object that contains a pointer to our smart pool. If the pool is destroyed, so is the shared_ptr, and any weak_ptr derived from it can be made aware of it.

The acquire() function

For now, just assume that ReturnToPool_Deleter is the deleter that returns the object to the pool, and in order to do so, needs a weak_ptr from this_ptr_.

An object is popped from the pool, and transferred to the "external ownership type", ptr_type. Whereas the pool would destroy the objects if deleted, the ptr_type returns the objects to the pool.

The magical ReturnToPool_Deleter

The deleter needs to be aware of the pool it came from, and whether it exists. That's where the weak_ptr comes in.
The operator() function is what gets called when the external object is deleted. This should either return it to the pool, or delete it itself using T's deleter D if the pool no longer exists.

3. Notes

3.1. Concurrency concerns

I wanted to keep the class simple for readability. However, if you plan to use this in a multi-threaded environment, some changes would need to be made. In particular add() and acquire() should have a lock_guard with the same mutex.

3.2. What if std::stack::push throws when returning an object to the pool?
The line:
is quite possibly not exception safe, as add() calls std::stack::push, which can throw std::bad_alloc. If this happens during destruction (which would be the case here), std::terminate is called and the program dies. To safeguard against this, you might instead chose to do:
If you run out of memory, clear up some by destroying an object instead of returning it to the pool. A good way to both mitigate a problem, and not crash and burn.


Thanks to:
T.C. and Jonathan Wakely for helpful suggestions on the stackoverflow question here .