So far, the answer seems to involve referring to boost implementation details, which doesn't thrill me.
I just wanted to create some memory-pools that could be used to allocate objects referred to through boost:shared_ptr<>.
I want to avoid non-pool memory-allocation, and for each shared-object allocation to involve one (and only one) allocation from one pool, i.e. to be as predictable and compact as possible.
This hand-written definition for a pool and an allocator seem to do what I want:
----------
template<typename T>
class my_pool
{
private:
char *rawStorage;
T *storage;
T *nextFree;
public:
my_pool(size_t S)
: storage(nullptr), nextFree(nullptr)
{
// Allocate memory.
rawStorage = new char[sizeof(T) * S];
storage = reinterpret_cast<T*>( rawStorage );
// All objects are free, initially.
for (size_t i = 0; i < S; ++i)
{
T **pFreeObject = reinterpret_cast<T**>(&storage[i]);
*pFreeObject = nextFree;
nextFree = reinterpret_cast<T*>(&storage[i]);
}
}
~my_pool()
{
// TODO: Verify that all allocated objects have been freed.
delete[] rawStorage;
}
T *allocate()
{
T *freeObject = nextFree;
if (nextFree != nullptr)
{
T **pFreeObject = reinterpret_cast<T**>(nextFree);
nextFree = *pFreeObject;
}
return freeObject;
}
void deallocate(T *obj)
{
T **pFreeObject = reinterpret_cast<T**>(obj);
*pFreeObject = nextFree;
nextFree = obj;
}
};
template<typename T>
class my_allocator : public at_boost::detail::sp_ms_deleter<T>
{
public:
typedef at_boost::detail::sp_counted_impl_pda<T *, at_boost::detail::sp_ms_deleter<T>, my_allocator<T> > shared_type;
typedef my_pool<shared_type> pool_type;
private:
pool_type &m_rPool;
void operator=(my_allocator const &other); // Disallow assignment.
public:
explicit my_allocator(pool_type &a_rPool) : m_rPool(a_rPool) { }
my_allocator(my_allocator const &other) : m_rPool(other.m_rPool) { }
template <typename U>
struct rebind
{
typedef my_allocator/*<U>*/ other;
};
shared_type *allocate(int iCount)
{
if (iCount == 1) // Only expected to allocate single objects.
return m_rPool.allocate();
return nullptr;
}
void deallocate(shared_type *obj, size_t iCount)
{
m_rPool.deallocate(obj);
}
};
----------
It still needs polish, but it's just a proof of concept.
Here's some code that uses the pool/allocator:
----------
my_allocator<int>::pool_type myPool (1024);
my_allocator<int> myAllocator(myPool);
boost::shared_ptr<int> pTest = boost::allocate_shared<int, my_allocator<int> >(myAllocator);
int iVal0 = *pTest;
*pTest = sizeof(my_allocator<int>::shared_type);
int iVal1 = *pTest;
----------
No big deal. Mostly, the test is to make sure that ::operator new() doesn't get called after the construction of the pool, and that seems to be true.
I'm surprised that my expected usage of memory-pools isn't more common.
Maybe using boost in an environment with constraints on dynamic memory allocation is unusual?
Also, at some point, I need to figure out why my definition of my_allocator<T>::rebind can't use <U> in the way that the standard boost allocators do.
I get an incomprehensible error-message when I do that:
----------
boost\smart_ptr\detail\sp_counted_impl.hpp(237): error C2664: 'my_allocator<T>::my_allocator(my_pool<at_boost::detail::sp_counted_impl_pda<P,D,A>> &)' : cannot convert parameter 1 from 'my_allocator<T>' to 'my_pool<T> &'
----------
Any comments on what I'm trying to do here?
-Steven