I recently needed to have a pool of objects and it would make everyones life a lot easier if those objects would automatically return themselves to the pool when they were done.
Here's my take on a shared pointer pool.
The pool itself is a list of raw pointers (old-school c pointers), but the pool returns a shared pointer. The raw pointer is wrapped in the shared pointer with a custom delete function. The delete function doesn't actually delete the pointer, it just returns it to the pool for a future request()
call. The pointers are added/removed from the end of the list in the hopes that the object will still be in the cpu cache.
When the pool itself is destroyed, the raw pointers are deleted as per normal. Hopefully all the shared objects have gone out of scope and have been returned to the pool by this time.
You'll probably want to derive from this class (or have it as a member variable and provide pass-through methods) and provide some custom create()
methods. Basically, you'll want to make the pool into a custom factory class.
Also, this class works well if turned into a singleton
.
#pragma once
#include <list>
#include <boost/shared_ptr.hpp>
#include <boost/bind.hpp>
#include <boost/noncopyable.hpp>
template<typename T>
class shared_pool : boost::noncopyable
{
public:
typedef shared_pool<T> pool_type;
typedef boost::shared_ptr<T> pointer;
typedef boost::shared_ptr<const T> const_pointer;
typedef std::list<T*> list;
shared_pool( size_t initial_size = 0, size_t grow_by = 1 )
{
_grow_by = grow_by;
make(initial_size);
}
virtual ~shared_pool()
{
free_pool();
}
size_t size() const { return _pool.size(); }
void grow_by( size_t n ) { _grow_by = n; }
pointer clone( const pointer& src )
{
pointer p = create();
*p = *src;
return p;
}
pointer request() { return create(); }
void free_pool()
{
for( typename list::iterator it = _pool.begin(); it != _pool.end(); ++it )
delete *it;
_pool.clear();
}
private:
void release( T* p )
{
_pool.push_back(p);
}
void make( size_t n )
{
for( unsigned i=0; i < n; i++ )
{
_pool.push_back( new T() );
}
}
pointer create()
{
if( _pool.empty() )
make( _grow_by );
T* p = _pool.back();
_pool.pop_back();
// magic is here, custom "deleter" puts raw pointer back in pool
return pointer(p, boost::bind(&pool_type::release, this, _1) );
}
list _pool;
size_t _grow_by;
};
So here's a slightly fleshed out example
struct Person {
std::string first_name;
std::string last_name;
};
class PersonPool : public shared_pool<Person>
{
public:
// make PersonPool a singleton
static PersonPool& instance() {
static PersonPool pool;
return pool;
}
pointer create( const std::string& first_name, const std::string& last_name )
{
pointer p = pool_type::request();
// p->clear(); // if the object in the pool needed some type of "resetting" you'd do it here
p->first_name = first_name;
p->last_name = last_name;
return p;
}
private:
PersonPool() : pool_type(5,1) {} // initial size of 5, grow by 1
~PersonPool();
};
int main() {
PersonPool::pointer person = PersonPool::instance().create( "John","Smith");
cout << person->first_name << " " << person->last_name << endl;
}
Edit (2017-02-06): I just learned about aliased shared_ptr, http://www.codesynthesis.com/~boris/blog/2012/04/25/shared-ptr-aliasing-constructor/. As I've implemented it, it's possible that the pointers returned by the pool could be able to outlive the pool itself, using the alias technique, that hole could probably be filled.