Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Copy stateful allocator: standard library allocator semantics and internal memory

Tags:

c++

stl

allocator

I am writing a collection of allocators, with the intention that they're to be used in very high performance environments, so a little bit of restricted usage (mediated by the compiler, not runtime errors) is desirable. I've been reading into the C++11 semantics of stateful allocators and how they're expected to be used by conforming containers.

I've pasted a simple allocator below which just contains a block of memory within the allocator object. In C++03, this was illegal.

template <typename T, unsigned N>
class internal_allocator {
private:
    unsigned char storage[N];
    std::size_t cursor;
public:
    typedef T value_type;
    internal_allocator() : cursor(0) {}
    ~internal_allocator() { }

    template <typename U>
    internal_allocator(const internal_allocator<U>& other) {
        // FIXME: What are the semantics here?
    }

    T* allocate(std::size_t n) {
        T* ret = static_cast<T*>(&storage[cursor]);
        cursor += n * sizeof(T);
        if (cursor > N)
            throw std::bad_alloc("Out of objects");
        return ret;
    }
    void deallocate(T*, std::size_t) {
        // Noop!
    }
};

In C++11, is this doable? What does it mean to copy a stateful allocator? Since the destination container invokes the copy constructor for all elements in the source container, must the memory inside the allocator be explicitly copied, or is default-construction enough?

This leads to the question, given performance as the ultimate goal, what are sane values for propagate_on_container_{copy,swap,move}? What does select_on_container_copy_construction return?

I'm happy to provide more details on request because this seems a rather nebulous issue -- at least to me =)

This contention arises from the definition that when a == b returns true for two instances of the same Allocator type, it is guaranteed that memory allocated with a may be deallocated with b. That seems to never be true for this allocator. The standard also states that when an allocator is copy-constructed, as in A a(b), a == b is guaranteed to return true.

like image 726
jared_schmitz Avatar asked Oct 01 '22 12:10

jared_schmitz


1 Answers

The allocator requirements say that copies of an allocator must be able to free each others' memory, so it is not generally possible to store the memory inside the allocator object.

This must be valid:

using IAllocChar = internal_allocator<char, 1024>;
IAllocChar::pointer p
IAllocChar a1;
{
  IAllocChar a2(a1);
  p = std::allocator_traits<IAllocChar>::allocate(a2, 1);
}
std::allocator_traits<IAllocChar>::deallocate(a1, p, 1)

So you need to store the actual memory outside the allocator object (or only use it in very restricted ways that ensure the object doesn't go out of scope while anything is referring to memory it owns).

You're also going to struggle with rebinding your internal_allocator, what should the following do?

using IAllocChar = internal_allocator<char, 1024>;
using IAllocInt = std::allocator_traits<IAllocChar>::rebind_alloc<int>;
IAllocChar ac;
auto pc = ac.allocate(1);  // got bored typing allocator_traits ;-)
IAllocInt  ai(ac);
auto pi = ai.allocate(1);
IAllocChar(ai).deallocate(pc, 1);
IAllocInt(ac).deallocate(pi, 1);
like image 133
Jonathan Wakely Avatar answered Oct 05 '22 11:10

Jonathan Wakely