Can I assume allocators don't hold their memory pool directly (and can therefore be copied)?

后端 未结 2 1052
抹茶落季
抹茶落季 2020-12-10 06:33

I\'m writing a container and would like to permit the user to use custom allocators, but I can\'t tell if I should pass allocators around by reference or by value.

I

2条回答
  •  旧巷少年郎
    2020-12-10 06:55

    In C++11 section 17.6.3.5 Allocator requirements [allocator.requirements] specifies the requirements for conforming allocators. Among the requirements are:

    X                    an Allocator class for type T
    ...
    a, a1, a2            values of type X&
    ...
    a1 == a2             bool          returns true only if storage
                                       allocated from each can be
                                       deallocated via the other.
                                       operator== shall be reflexive,
                                       symmetric, and transitive, and
                                       shall not exit via an exception.
    ...
    X a1(a);                           Shall not exit via an exception.
                                       post: a1 == a
    

    I.e. when you copy an allocator, the two copies are required to be able to delete each other's pointers.

    Conceivably one could put internal buffers into allocators, but copies would have to keep a list of other's buffers. Or perhaps an allocator could have an invariant that deallocation is always a no-op because the pointer always comes from an internal buffer (either from your own, or from some other copy).

    But whatever the scheme, copies must be "cross-compatible".

    Update

    Here is a C++11 conforming allocator that does the "short string optimization". To make it C++11 conforming, I had to put the "internal" buffer external to the allocator so that copies are equal:

    #include 
    
    template 
    class arena
    {
        static const std::size_t alignment = 16;
        alignas(alignment) char buf_[N];
        char* ptr_;
    
        std::size_t 
        align_up(std::size_t n) {return n + (alignment-1) & ~(alignment-1);}
    
    public:
        arena() : ptr_(buf_) {}
        arena(const arena&) = delete;
        arena& operator=(const arena&) = delete;
    
        char* allocate(std::size_t n)
        {
            n = align_up(n);
            if (buf_ + N - ptr_ >= n)
            {
                char* r = ptr_;
                ptr_ += n;
                return r;
            }
            return static_cast(::operator new(n));
        }
        void deallocate(char* p, std::size_t n)
        {
            n = align_up(n);
            if (buf_ <= p && p < buf_ + N)
            {
                if (p + n == ptr_)
                    ptr_ = p;
            }
            else
                ::operator delete(p);
        }
    };
    
    template 
    class stack_allocator
    {
        arena& a_;
    public:
        typedef T value_type;
    
    public:
        template  struct rebind {typedef stack_allocator other;};
    
        explicit stack_allocator(arena& a) : a_(a) {}
        template 
            stack_allocator(const stack_allocator& a)
                : a_(a.a_) {}
        stack_allocator(const stack_allocator&) = default;
        stack_allocator& operator=(const stack_allocator&) = delete;
    
        T* allocate(std::size_t n)
        {
            return reinterpret_cast(a_.allocate(n*sizeof(T)));
        }
        void deallocate(T* p, std::size_t n)
        {
            a_.deallocate(reinterpret_cast(p), n*sizeof(T));
        }
    
        template 
        friend
        bool
        operator==(const stack_allocator& x, const stack_allocator& y);
    
        template  friend class stack_allocator;
    };
    
    template 
    bool
    operator==(const stack_allocator& x, const stack_allocator& y)
    {
        return N == M && &x.a_ == &y.a_;
    }
    
    template 
    bool
    operator!=(const stack_allocator& x, const stack_allocator& y)
    {
        return !(x == y);
    }
    

    It could be used like this:

    #include 
    
    template  using A = stack_allocator;
    template  using Vector = std::vector>;
    
    int main()
    {
        const std::size_t N = 1024;
        arena a;
        Vector v{A(a)};
        v.reserve(100);
        for (int i = 0; i < 100; ++i)
            v.push_back(i);
        Vector v2 = std::move(v);
        v = v2;
    }
    

    All allocations for the above problem are drawn from the local arena which is 1 Kb in size. You should be able to pass this allocator around by value or by reference.

提交回复
热议问题