HiveBrain v1.2.0
Get Started
← Back to all entries
patterncppMinor

Simple Pool Allocator

Submitted by: @import:stackexchange-codereview··
0
Viewed 0 times
poolallocatorsimple

Problem

This is a simple pool allocator, based (to at least some degree) on a previous answer. The basic idea is pretty simple: allow a user to allocate objects of some type quickly (in relatively large contiguous chunks) and only free objects when the allocator itself is freed, then destroy all the objects and free the memory they occupy.

#include 
#include 

template 
class Allocator {
    const size_t pool_size = T_per_page * sizeof(T);
    std::vector pools;
    size_t next_pos;

    void alloc_pool() {
        next_pos = 0;       
        void *temp = operator new(pool_size);
        pools.push_back(static_cast(temp));
    }

public:
    Allocator() {
        alloc_pool();
    }

    T* operator()(T const &x) {
        if (next_pos == T_per_page)
            alloc_pool();

        T *ret = new(pools.back() + next_pos) T(x);
        ++next_pos;
        return ret;
    }

    ~Allocator() {
        while (!pools.empty()) {
            T *p = pools.back();
            for (size_t pos = T_per_page; pos > 0; --pos)
                p[pos - 1].~T();
            operator delete(static_cast(p));
            pools.pop_back();
        }
    }
};

#ifdef TEST

struct list_node {
    int val;
    list_node *next;

    list_node(int x) : val(x), next(nullptr) {}

    friend std::ostream &operator alloc;

int main() {
    for (int i = 0; i < 20; i++) {
        list_node *j = alloc(i + 1000);
    }
}

#endif


I'd welcome any and all suggestions (including adding specialization to avoid executing destructors on trivially destructible types). I'm also wondering (sort of thinking out loud) whether it would make sense to use a geometric allocation strategy, similar to std::vector's, to (perhaps) fit allocation time to size more effectively.

Solution

Allocation of raw memory

Rather than using operator new you could use std::get_temporary_buffer(n). This allocates contiguous "correctly aligned" storage for enough memory to store approx n objects of type T (its non binding contract read documents).
Constructor

It may be more efficient to use normal constructor than the copy constructor. You should also provide the move constructor.

T* operator()(T&& x);
T* operator()(T const &x);
template
T* operator()(Args... args);


Destructor

Yes you can optimize the destructor if Ts destructor is guranteed to do nothing.

template
struct SimpleDestructableTrivialy
{
    static constexpr bool value = std::is_trivially_destructible::value;
};
template::value>
class SimpleDestroy
{
    public:
        void destroyElements(T* p, std::size_t T_per_page);
};
template
class SimpleDestroy
{
    public:
        void destroyElements(T* p, std::size_t T_per_page) {}
};
template
class SimpleDestroy
{
    public:
        void destroyElements(T* p, std::size_t T_per_page)
        {
            for (size_t pos = T_per_page; pos > 0; --pos)
                p[pos - 1].~T();
        }
};

Code Snippets

T* operator()(T&& x);
T* operator()(T const &x);
template<typename... Args>
T* operator()(Args... args);
template<typename T>
struct SimpleDestructableTrivialy
{
    static constexpr bool value = std::is_trivially_destructible<T>::value;
};
template<typename T, bool = SimpleDestructableTrivialy<T>::value>
class SimpleDestroy
{
    public:
        void destroyElements(T* p, std::size_t T_per_page);
};
template<typename T>
class SimpleDestroy<T, true>
{
    public:
        void destroyElements(T* p, std::size_t T_per_page) {}
};
template<typename T>
class SimpleDestroy<T, false>
{
    public:
        void destroyElements(T* p, std::size_t T_per_page)
        {
            for (size_t pos = T_per_page; pos > 0; --pos)
                p[pos - 1].~T();
        }
};

Context

StackExchange Code Review Q#127258, answer score: 7

Revisions (0)

No revisions yet.