how to write a stateful allocator with gcc and stl

M

Mike

Hi,

I have a simple "memPool' class that simply maintains a linked list of
chunks from which allocation requests are made. The whole thing is
deleted all at once upon destruction. So far, so good.

Then I tried to make an allocator for this and use it with std::list
with no success. I modeled my allocator code from gcc's
new_allocator.h. I modified the allocate() and deallocate() functions
and copy constructors, and added a "memPool" member and a
corresponding constructor to initialize it.

Using gdb, I see that when the list is constructed, it uses the
allocator's default constructor instead of using the allocator's copy
constructor, and thus, the allocator state is lost. Of course this
segfaults during the first attempt to use the (garbage) psMemPool
pointer.

Is it even possible to achieve what I want? It feels like I am close
but I don't really know where to go from here.

Please find my sample code below.

Thanks in advance,
Mike

-----------------------------------------------------------------------------------------------------------------------
////////////////////////////// memory pool
#include <cstdlib>

class tMemPool
{
private:
struct tNode
{
char *pcMemory;
tNode *psNext;
};
unsigned int uiOffset, uiSize, uiAlign;
tNode *psHead, *psCurr;
tNode * addNode(void);
protected:
void * alloc(size_t _ui) {return malloc(_ui);}
void dealloc(void *_pv) {free(_pv);}
public:
tMemPool(unsigned int _uiSize, unsigned int _uiLog2Align = 3);
~tMemPool();
void * allocate(size_t _ui);
};

tMemPool::tMemPool(unsigned int _uiSize, unsigned int _uiLog2Align) :
uiOffset(0), uiSize(_uiSize)
{
uiAlign = (1 << _uiLog2Align) - 1;
psHead = psCurr = addNode();
}

tMemPool::~tMemPool()
{
while (psHead)
{
tNode *psCurr = psHead->psNext;
dealloc(psHead);
psHead = psCurr;
}
}

tMemPool::tNode * tMemPool::addNode(void)
{
tNode *ps = reinterpret_cast<tNode *>(alloc(sizeof(tNode) +
uiSize));
ps->pcMemory = reinterpret_cast<char *>(ps + 1);
ps->psNext = NULL;
return ps;
}

void * tMemPool::allocate(size_t _ui)
{
void *pv;

if (_ui > (uiSize - uiOffset))
{
if (uiSize < _ui)
uiSize = _ui;
psCurr->psNext = addNode();
psCurr = psCurr->psNext;
uiOffset = 0;
}

pv = reinterpret_cast<void *>(psCurr->pcMemory + uiOffset);
uiOffset += (_ui + uiAlign) & ~uiAlign;
return pv;
}

////////////////////////////// memory pool allocator

#include <cstddef>

template<typename T>
class tMemPoolAllocator
{
public:
typedef size_t size_type;
typedef ptrdiff_t difference_type;
typedef T * pointer;
typedef const T * const_pointer;
typedef T& reference;
typedef const T & const_reference;
typedef T value_type;

template<typename U> struct rebind {typedef tMemPoolAllocator<U>
other;};
tMemPoolAllocator(void) throw() : psMemPool(0) {}
tMemPoolAllocator(tMemPool *_ps) throw() : psMemPool(_ps) {}
tMemPoolAllocator(const tMemPoolAllocator &_s) throw() :
psMemPool(_s.psMemPool) {}
template<typename U> tMemPoolAllocator(const tMemPoolAllocator<U>
&_s) throw() : psMemPool(_s.psMemPool) {}
~tMemPoolAllocator() throw() {}
pointer address(reference _x) const {return &_x;}
const_pointer address(const_reference _x) const {return &_x;}
pointer allocate(size_type _n, const void* = 0) {return
static_cast<T*>(psMemPool->allocate(_n * sizeof(T)));}
void deallocate(pointer _p, size_type) {}
size_type max_size() const throw() {return size_t(-1) / sizeof(T);}
void construct(pointer _p, const T& _val) {::new(_p) T(_val);}
void destroy(pointer _p) {_p->~T();}
// state
tMemPool *psMemPool;
};

template<typename T>
inline bool operator==(const tMemPoolAllocator<T> &_s0, const
tMemPoolAllocator<T> &_s1) {return _s0.psMemPool == _s1.psMemPool;}

template<typename T>
inline bool operator!=(const tMemPoolAllocator<T> &_s0, const
tMemPoolAllocator<T> &_s1) {return _s0.psMemPool != _s1.psMemPool;}

////////////////////////////// simple test

#include <list>
int main(void)
{
tMemPool sPool(1024, 2);
tMemPoolAllocator<int> sPoolAllocator(&sPool);

std::list<int, tMemPoolAllocator<int> > l;
l.push_back(0);

return 0;
}
 
M

Mike

I forgot to mention that I am using
gcc version 4.2.3 20071123 (prerelease) (Debian 4.2.2-4)
 
L

Lance Diduck

Using gdb, I see that when the list is constructed, it uses the
allocator's default constructor instead of using the allocator's copy
constructor, and thus, the allocator state is lost.  Of course this
segfaults during the first attempt to use the (garbage) psMemPool
pointer.

Is it even possible to achieve what I want?  It feels like I am close
but I don't really know where to go from here.
Yes it is possible.
One rule of thumb when using stateufl STL allocators is NOT to allow
default construction. Then your sample would not compile, showing
where the error is:

tMemPool sPool(1024, 2);
tMemPoolAllocator<int> sPoolAllocator(&sPool);
std::list<int, tMemPoolAllocator<int> > l; //BOOOM!!!!!
//this line should read
std::list<int, tMemPoolAllocator<int> > l(sPoolAllocator);
l.push_back(0); //should be OK

For more info, read http://www.lancediduck.com/papers/Cpp/StatefulSTL.pdf
and http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2007/n2486.pdf
Lance
 
M

Mike

Yep - I should have noticed that stl::list's default constructor took
an allocator for its (optional) argument:

explicit
list(const allocator_type& __a = allocator_type())
: _Base(__a) { }

I've never had a reason to construct a list with a specific allocator
instance before.
Everything works perfectly now.

Thanks,
Mike
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

Forum statistics

Threads
473,744
Messages
2,569,484
Members
44,903
Latest member
orderPeak8CBDGummies

Latest Threads

Top