O
Oliver S.
I was often annoyed about the performance of std::basic-string because
of complex memory-allocators working in the background to found the work
of std::allocator<T>. Basically, the performance doesn't reeach the per-
formance of a stack-based C-string in most cases. But nevertheless, the
comfort and the implicit protection against buffer-overflows makes std::
basic_string woth to be used. So I searched for a way to have my own
stack-based memory-allocation. I came up with the following idea which
resulted in a class I called the lallocator (derived from local alloca-
tor; this sounds funny in german because to "lall" means to babble in
german *g*).
First, we have the lallocator-class which is an interface to the locally
allocated storage for the stl-object (my lallocator can't be used for
strings only). This class is the same for all sizes of local stack-based
pools to prevent that we would compile derived stl-classes for any size
we use. Second, there's a class called lallocator_buffer_if which is the
interface to the local storage we allocated for the lallocator. I defined
this interface for all sizes of locally allocated pools to prevent any
secial-buffer-size-compiling I mentioned before. Third there's a dervied
class of lallocator_buffer_if<T>, called lallocator_buffer<T, buffers,
size>; T is the usual data-type in the buffers, buffers is the number of
buffers which can be allocated by the lallocator and size is the size of
each buffer. Whenever someone tries to do an allocate() on the lallocator
and there's a free buffer and the buffer is large enough to satisfy the
allocation-request, we'll allocate the buffer from the pool; otherwise
we fall back to std::allocator.
Here's an example of how this is used:
typedef std::basic_string<char, char_traits<char>, lallocator<char> >
lallostring;
lallocator_buffer<char, 5, 128> lbc;
lallocator<char> lallo( &lbc );
lallostring lsTest( lallo );
lsTest = "sdsdass";
Unfortunately my compiler isn't able to eat the following code
to prevent explicit instanciation of a lallocator<T>-object:
lallocator_buffer<char, 5, 128> lbc;
lallostring lsTest( lallocator<char>( &lbc ) );
Does anyone know if there's a conformance-problem here or is this
just a bug of my compiler?
So here's my lalloator (it isn't fully stl-conformant and I used a single
trick to prevent having an additional pointer in lallocator_buffer_if which
causes my code not to work on theoretical C++-implementations; sorry to all
religious developers):
template<typename T>
class lallocator_buffer_if
{
protected:
template<typename T, std::size_t buffers, std::size_t buffer_size>
friend class lallocator_buffer;
template<typename T>
friend class lallocator;
private:
lallocator_buffer_if() {}
T *pop_buffer();
void push_buffer( T *pt );
bool is_yours( T *pt );
protected:
union buffer_header
{
buffer_header *pbhNextFree;
T at[1];
};
protected:
std::size_t m_buffer_size;
buffer_header *m_pbhFirstFree;
buffer_header *m_pbhBufferEnd;
};
template<typename T>
inline
T *lallocator_buffer_if<T>:op_buffer()
{
buffer_header *pbh;
if( (pbh = m_pbhFirstFree) == NULL )
return NULL;
return m_pbhFirstFree = pbh->pbhNextFree,
&pbh->at[0];
}
template<typename T>
inline
void lallocator_buffer_if<T>:ush_buffer( T *pt )
{
buffer_header *pbh;
pbh = (buffer_header *)pt;
pbh->pbhNextFree = m_pbhFirstFree;
m_pbhFirstFree = pbh;
}
template<typename T>
inline
bool lallocator_buffer_if<T>::is_yours( T *pt )
{
return (void*)pt >= (void *)this &&
pt < &m_pbhBufferEnd->at[0];
}
template<typename T, std::size_t buffers, std::size_t buffer_size>
class lallocator_buffer : public lallocator_buffer_if<T>
{
public:
lallocator_buffer();
private:
union buffer
{
buffer_header bh;
T atUnReferenced[buffer_size];
};
private:
buffer aBuffers[buffers];
};
template<typename T, std::size_t buffers, std::size_t buffer_size>
inline
lallocator_buffer<T, buffers, buffer_size>::lallocator_buffer()
{
buffer *pbuf,
*pbufNext;
for( (pbuf = &aBuffers[buffers - 1],
pbufNext = NULL);
pbuf >= aBuffers;
(pbufNext = pbuf,
pbuf -= 1) )
pbuf->bh.pbhNextFree = &pbufNext->bh;
m_buffer_size = buffer_size;
m_pbhBufferEnd = &aBuffers[buffers].bh;
m_pbhFirstFree = &aBuffers[0].bh;
}
template<typename T>
class lallocator : public std::allocator<T>
{
public:
lallocator( lallocator_buffer_if<T> *plbi );
lallocator( lallocator const &lc );
~lallocator() {};
pointer allocate( size_type count, void *hint = NULL );
void deallocate( pointer ptr, size_type count );
lallocator &operator =( lallocator const &lc );
public:
template<class Other>
struct rebind
{
typedef lallocator<Other> other;
};
private:
lallocator_buffer_if<T> *m_plbi;
};
template<typename T>
inline
lallocator<T>::lallocator( lallocator_buffer_if<T> *plbi ) :
std::allocator<T>()
{
m_plbi = plbi;
}
template<typename T>
inline
lallocator<T>::lallocator( lallocator const &lc ) :
std::allocator<T>( lc )
{
m_plbi = lc.m_plbi;
}
template<typename T>
inline
typename lallocator<T>:ointer lallocator<T>::allocate( size_type count,
void *hint )
{
T *pt;
if( count > m_plbi->m_buffer_size ||
(pt = m_plbi->pop_buffer()) == NULL )
return std::allocator<T>::allocate( count );
return pt;
}
template<typename T>
inline
void lallocator<T>::deallocate( pointer ptr, size_type count )
{
if( !m_plbi->is_yours( ptr ) )
return (void)std::allocator<T>::deallocate( ptr, count );
m_plbi->push_buffer( ptr );
}
template<typename T>
inline
typename lallocator<T> &lallocator<T>:perator =( lallocator const &lc )
{
return m_plbi = lc.m_plbi,
*this;
}
[ See http://www.gotw.ca/resources/clcm.htm for info about ]
[ comp.lang.c++.moderated. First time posters: Do this! ]
of complex memory-allocators working in the background to found the work
of std::allocator<T>. Basically, the performance doesn't reeach the per-
formance of a stack-based C-string in most cases. But nevertheless, the
comfort and the implicit protection against buffer-overflows makes std::
basic_string woth to be used. So I searched for a way to have my own
stack-based memory-allocation. I came up with the following idea which
resulted in a class I called the lallocator (derived from local alloca-
tor; this sounds funny in german because to "lall" means to babble in
german *g*).
First, we have the lallocator-class which is an interface to the locally
allocated storage for the stl-object (my lallocator can't be used for
strings only). This class is the same for all sizes of local stack-based
pools to prevent that we would compile derived stl-classes for any size
we use. Second, there's a class called lallocator_buffer_if which is the
interface to the local storage we allocated for the lallocator. I defined
this interface for all sizes of locally allocated pools to prevent any
secial-buffer-size-compiling I mentioned before. Third there's a dervied
class of lallocator_buffer_if<T>, called lallocator_buffer<T, buffers,
size>; T is the usual data-type in the buffers, buffers is the number of
buffers which can be allocated by the lallocator and size is the size of
each buffer. Whenever someone tries to do an allocate() on the lallocator
and there's a free buffer and the buffer is large enough to satisfy the
allocation-request, we'll allocate the buffer from the pool; otherwise
we fall back to std::allocator.
Here's an example of how this is used:
typedef std::basic_string<char, char_traits<char>, lallocator<char> >
lallostring;
lallocator_buffer<char, 5, 128> lbc;
lallocator<char> lallo( &lbc );
lallostring lsTest( lallo );
lsTest = "sdsdass";
Unfortunately my compiler isn't able to eat the following code
to prevent explicit instanciation of a lallocator<T>-object:
lallocator_buffer<char, 5, 128> lbc;
lallostring lsTest( lallocator<char>( &lbc ) );
Does anyone know if there's a conformance-problem here or is this
just a bug of my compiler?
So here's my lalloator (it isn't fully stl-conformant and I used a single
trick to prevent having an additional pointer in lallocator_buffer_if which
causes my code not to work on theoretical C++-implementations; sorry to all
religious developers):
template<typename T>
class lallocator_buffer_if
{
protected:
template<typename T, std::size_t buffers, std::size_t buffer_size>
friend class lallocator_buffer;
template<typename T>
friend class lallocator;
private:
lallocator_buffer_if() {}
T *pop_buffer();
void push_buffer( T *pt );
bool is_yours( T *pt );
protected:
union buffer_header
{
buffer_header *pbhNextFree;
T at[1];
};
protected:
std::size_t m_buffer_size;
buffer_header *m_pbhFirstFree;
buffer_header *m_pbhBufferEnd;
};
template<typename T>
inline
T *lallocator_buffer_if<T>:op_buffer()
{
buffer_header *pbh;
if( (pbh = m_pbhFirstFree) == NULL )
return NULL;
return m_pbhFirstFree = pbh->pbhNextFree,
&pbh->at[0];
}
template<typename T>
inline
void lallocator_buffer_if<T>:ush_buffer( T *pt )
{
buffer_header *pbh;
pbh = (buffer_header *)pt;
pbh->pbhNextFree = m_pbhFirstFree;
m_pbhFirstFree = pbh;
}
template<typename T>
inline
bool lallocator_buffer_if<T>::is_yours( T *pt )
{
return (void*)pt >= (void *)this &&
pt < &m_pbhBufferEnd->at[0];
}
template<typename T, std::size_t buffers, std::size_t buffer_size>
class lallocator_buffer : public lallocator_buffer_if<T>
{
public:
lallocator_buffer();
private:
union buffer
{
buffer_header bh;
T atUnReferenced[buffer_size];
};
private:
buffer aBuffers[buffers];
};
template<typename T, std::size_t buffers, std::size_t buffer_size>
inline
lallocator_buffer<T, buffers, buffer_size>::lallocator_buffer()
{
buffer *pbuf,
*pbufNext;
for( (pbuf = &aBuffers[buffers - 1],
pbufNext = NULL);
pbuf >= aBuffers;
(pbufNext = pbuf,
pbuf -= 1) )
pbuf->bh.pbhNextFree = &pbufNext->bh;
m_buffer_size = buffer_size;
m_pbhBufferEnd = &aBuffers[buffers].bh;
m_pbhFirstFree = &aBuffers[0].bh;
}
template<typename T>
class lallocator : public std::allocator<T>
{
public:
lallocator( lallocator_buffer_if<T> *plbi );
lallocator( lallocator const &lc );
~lallocator() {};
pointer allocate( size_type count, void *hint = NULL );
void deallocate( pointer ptr, size_type count );
lallocator &operator =( lallocator const &lc );
public:
template<class Other>
struct rebind
{
typedef lallocator<Other> other;
};
private:
lallocator_buffer_if<T> *m_plbi;
};
template<typename T>
inline
lallocator<T>::lallocator( lallocator_buffer_if<T> *plbi ) :
std::allocator<T>()
{
m_plbi = plbi;
}
template<typename T>
inline
lallocator<T>::lallocator( lallocator const &lc ) :
std::allocator<T>( lc )
{
m_plbi = lc.m_plbi;
}
template<typename T>
inline
typename lallocator<T>:ointer lallocator<T>::allocate( size_type count,
void *hint )
{
T *pt;
if( count > m_plbi->m_buffer_size ||
(pt = m_plbi->pop_buffer()) == NULL )
return std::allocator<T>::allocate( count );
return pt;
}
template<typename T>
inline
void lallocator<T>::deallocate( pointer ptr, size_type count )
{
if( !m_plbi->is_yours( ptr ) )
return (void)std::allocator<T>::deallocate( ptr, count );
m_plbi->push_buffer( ptr );
}
template<typename T>
inline
typename lallocator<T> &lallocator<T>:perator =( lallocator const &lc )
{
return m_plbi = lc.m_plbi,
*this;
}
[ See http://www.gotw.ca/resources/clcm.htm for info about ]
[ comp.lang.c++.moderated. First time posters: Do this! ]