7

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.

jared_schmitz
  • 583
  • 2
  • 10
  • 2
    According to https://youtu.be/0MdSJsCTRkY , this allocator is a bad example of a stateful allocator. It seems that this kind of allocator is precisely the kind that forces you to have a “non-propagating” allocator, since it cannot be copied in a meaningful way. (IMO a bad motivation for an over complicated feature). I think it is sane to see allocator as a pointer (decorated with traits and features) to a heap. See here also https://stackoverflow.com/questions/54703727/allocator-propagation-policies-in-your-new-modern-c-containers – alfC Nov 30 '20 at 10:19

1 Answers1

6

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);
Jonathan Wakely
  • 166,810
  • 27
  • 341
  • 521
  • This makes sense to me. So such an allocator cannot be copied in a semantically-conforming way. It's too bad the allocator_traits types don't allow for making a container uncopyable based on the properties of the allocator, but I suppose that was of limited usefulness compared to the implementation overhead. Side note: Thanks for working on allocator-awareness in libstdc++ containers =) – jared_schmitz Mar 12 '14 at 18:20
  • Yes, if you could ensure a container was uncopyable then you might get away with it even though your allocator doesn't conform to all the requirements, although it would depend on the implementation not making unnecessary internal copies (which is a reasonable assumption, but not guaranteed). Thanks for the thanks :) – Jonathan Wakely Mar 12 '14 at 18:27