Search code examples
c++vectorallocator

Can a vector hold two allocators or use different allocators to allocate and construct/destroy?


I'm trying to implement my custom vector. In some case, I decide to place capacity in front of the real data, and access capacity by shifting the pointer. Assuming that sizeof(capacity) == 4 while sizeof(value_type) == 512, using allocator<T> will cause memory fragment undoubtedly, which might be unacceptable. Therefore, I can't use allocator<T>::allocate to allocate the whole chunk memory. However, I can hold two allocators(allocator<T> and rebind_alloc<byte>) at the same time, then allocate memory by rebind_alloc<byte> and constrcut value by allocator<T>. I wonder whether it satisfy standard requirements. Or, is there any other good idea?


Solution

  • Holding two allocators for one container might be hard to maintain, so according to my problem, what I need is a stateful allocator. There is already a ready-made paradigm in the standard library, which is called std::scoped_allocator_adapater.

    The std::scoped_allocator_adaptor class template is an allocator which can be used with multilevel containers (vector of sets of lists of tuples of maps, etc). It is instantiated with one outer allocator type OuterAlloc and zero or more inner allocator types InnerAlloc.... A container constructed directly with a scoped_allocator_adaptor uses OuterAlloc to allocate its elements, but if an element is itself a container, it uses the first inner allocator. The elements of that container, if they are themselves containers, use the second inner allocator, etc. If there are more levels to the container than there are inner allocators, the last inner allocator is reused for all further nested containers.

    That std::scoped_allocator_adapter is what I need here. So, I implement my allocator like this:

    template <class AllocByte, class AllocTy>
    class MyAlloc : AllocTy, AllocByte {
        using alty_traits   = std::allocator_traits<AllocTy>;
        using albyte_traits = std::allocator_traits<AllocByte>;
    
    public:
        using alty_type   = AllocTy;
        using albyte_type = AllocByte;
    
        // other functions and type alias
    
        albyte_type& byte_allocator() noexcept { return static_cast<AllocByte&>(*this); }
    
        const albyte_type& byte_allocator() const noexcept { return static_cast<const AllocByte&>(*this); }
    
        alty_type& value_allocator() noexcept { return static_cast<AllocTy&>(*this); }
    
        const alty_type& value_allocator() const noexcept { return static_cast<const AllocTy&>(*this); }
    
        pointer allocate(size_type n) { return albyte_traits::allocate(byte_allocator(), n); }
    
        void deallocate(pointer ptr, size_type n) { return albyte_traits::deallocate(byte_allocator(), ptr, n); }
    
        size_type max_size() const { return albyte_traits::max_size(byte_allocator()); }
    
        template <class _Ty, class... _Args>
        void construct(_Ty* ptr, _Args&&... args) {
            alty_traits::construct(value_allocator(), ptr, std::forward<_Args>(args)...);
        }
    
        template <class _Ty>
        void destroy(_Ty* ptr) {
            alty_traits::destroy(value_allocator(), ptr);
        }
    };