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:
- If an acquired objects is deleted, it should return to the pool.
- ... if pool itself is already deleted, the object should instead be deallocated.
- 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
.