I wrote an STL allocator which uses boost::pool
to allocate the memory for std::vector
container.
This is my first time I implement an allocator and while working on this class I found that STL containers (std::vector
for example) copy, destruct, create many times the assigned allocator during their lifetime. For example, during pushing operation into std::vector
, the allocator's copy constructor and destructor are called several times. Due to this I also had to wrap the boost::pool
with a smart pointer because when I was passing the pool as external argument and then destroying it explicitly I was getting crashes on program exist inside the allocator because some clones of it were still trying to access the pool. I am still not sure I completely understand the mechanics of how STL containers treat allocators during their lifetime. Therefore I am asking to review this class implementation.
Also I would like to have the answer to the following questions:
Is this the right way to use
boost::pool
for raw memory allocation? My intention was to allocate a large pool (in this example it is just 4096 bytes) of single byte blocks to mimic raw heap memory arena, because the allocator interface requests the memory in varying sizes multiplied by the data size.If the answer to the question above is positive, can I share the same allocator instance between several
std::vector
instances (with the same data type)? Besides thread unsafety, what are the possible caveats or pitfalls I need to take into account?
template<typename T>
class Allocator{
friend class Allocator;
private:
typedef std::shared_ptr<boost::pool<> > PoolSP;
PoolSP m_pool;
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;
/// Default constructor
Allocator() throw()
{
m_allocUsedSize = 0;
m_pool.reset(new boost::pool<>(sizeof(uint8_t), 4096));
}
/// Copy constructor
Allocator(const Allocator& other) throw()
{
*this = other;
}
/// Destructor
~Allocator()
{
}
/// Copy constructor with another type
template<typename U>
Allocator(const Allocator<U> &other) throw()
{
*this = other;
}
/// Copy
Allocator<T>& operator=(const Allocator& other)
{
m_pool = other.m_pool;
return *this;
}
/// Copy with another type
template<typename U>
Allocator& operator=(const Allocator<U>& other)
{
m_pool = other.m_pool;
return *this;
}
/// Get address of a reference
pointer address(reference x) const
{
return &x;
}
/// Get const address of a const reference
const_pointer address(const_reference x) const
{
return &x;
}
/// Allocate n elements of type T
pointer allocate(size_type n, const void* = 0)
{
size_type size = n * sizeof(value_type);
m_allocUsedSize += size;
return (pointer)m_pool->ordered_malloc(size); //
}
/// Free memory of pointer p
void deallocate(void* p, size_type n)
{
size_type size = n * sizeof(value_type);
m_allocUsedSize -= size;
m_pool->ordered_free(p);
}
/// Call the constructor of p
void construct(pointer p, const T& val)
{
// Placement new
new ((T*)p) T(val);
}
/// Call the constructor of p with many arguments. C++11
template<typename U, typename... Args>
void construct(U* p, Args&&... args)
{
// Placement new
::new((void*)p) U(std::forward<Args>(args)...);
}
/// Call the destructor of p
void destroy(pointer p)
{
p->~T();
}
/// Call the destructor of p of type U
template<typename U>
void destroy(U* p)
{
p->~U();
}
void reset()
{
m_pool->purge_memory();
m_pool.reset();
}
/// A struct to rebind the allocator to another allocator of type U
template<typename U>
struct rebind
{
typedef Allocator<U> other;
};
};
size_t m_allocUsedSize;
This implementation works and is in fact almost twice faster than using the default std::vector
allocator. But memory errors are nasty things. That's one of the reasons why I want to see an input from more people.