简体   繁体   中英

Custom C++ Allocator cause error in : _Container_base12::_Orphan_all_unlocked_v3()

So I intend to use this stack-based allocator for std::vector, and I use 2 arrays for the allocation(because vectors grow and copy the old buffer to the new).

Here's the full code:

#include <stdio.h>
#include <string.h>
#include <math.h>
#include <vector>
#include <cstddef>
#include <cassert>
#include <array>

using size_t = std::size_t;
using byte = std::byte;

template <class T, size_t capacity = 512>
class stack_allocator
{
public:
    using value_type = T;

    using pointer = value_type*;
    using const_pointer = typename std::pointer_traits<pointer>::template rebind<value_type const>;
    using void_pointer = typename std::pointer_traits<pointer>::template rebind<void>;
    using const_void_pointer = typename std::pointer_traits<pointer>::template rebind<const void>;

    using difference_type = typename std::pointer_traits<pointer>::difference_type;
    using size_type = std::make_unsigned_t<difference_type>;

    template <class U> struct rebind { typedef stack_allocator<U, capacity> other; };

    stack_allocator() noexcept {};  // not required, unless used
    ~stack_allocator() noexcept = default;

    //stack_allocator(stack_allocator&&) = delete;
    //stack_allocator& operator=(stack_allocator&&) = delete;
    //stack_allocator(const stack_allocator&) = delete;
    //stack_allocator& operator=(const stack_allocator&) = delete;

    template <class U>
    stack_allocator(stack_allocator<U> const&) noexcept {}
    // ? is n already aligned ?
    inline pointer allocate(size_t n)
    {
        constexpr auto max_size_allowed = (capacity>>1);
        auto size = n * sizeof(value_type); 
        if (size > max_size_allowed)
        {
            return static_cast<pointer>(::operator new (size));
        }
        else
        {
            m_Index = !m_Index;
            return static_cast<pointer>(static_cast<void*>(&m_Array[static_cast<size_t>(m_Index)][0]));
        }
    }

    inline void deallocate(pointer p, size_t n) noexcept
    {
        constexpr auto max_size_allowed = (capacity>>1);
        auto size = n * sizeof(value_type);
        if (size > max_size_allowed)
            ::operator delete(p);
        else
        {
            // do nothing
        }
    }

    inline pointer allocate(size_t n, const_void_pointer)
    {
        return allocate(n);
    }

    template <class U, class ...Args>
    void construct(U* p, Args&& ...args)
    {
        ::new(p) U(std::forward<Args>(args)...);
    }

    template <class U>
    void destroy(U* p) noexcept
    {
        p->~U();
    }

    inline constexpr size_t max_size() const noexcept
    {
        return std::numeric_limits<std::size_t>::max() / sizeof(T);
    }

    stack_allocator select_on_container_copy_construction() const
    {
        return *this;
    }

    using propagate_on_container_copy_assignment = std::true_type;
    using propagate_on_container_move_assignment = std::false_type;
    using propagate_on_container_swap = std::true_type;
    using is_always_equal = std::true_type;

protected:
    //const bool is_pointer_in_range(byte* p) const noexcept { return (p >= &m_Array[0]) && (p <= &m_Array[capacity - 1]); }

    bool m_Index{ false };
    byte m_Array[2][(capacity >> 1)]{ {static_cast<byte>(0)} };
};

template <class T, size_t capacity, class U>
bool operator==(stack_allocator<T, capacity> const&, stack_allocator<U, capacity> const&) noexcept
{
    return true;
}

template <class T, size_t capacity, class U>
bool operator!=(stack_allocator<T, capacity> const& x, stack_allocator<U, capacity> const& y) noexcept
{
    return false;
}

int main(int argc, char** argv)
{
    std::vector<int, stack_allocator<int, 512>> stackVec;
    stackVec.push_back(1);
    stackVec.push_back(2);
    stackVec.push_back(3);

    return 0;
}

Somehow the code cause crash: enter image description here

enter image description here

I've notice that the "_Container_base12" inherits from the allocator, so I guess there must be something wrong with my "stack_allocator" implementation. But I can't figure it out.

Hope to know what is the correct way of writing an allocator.

Thanks to Igor Tandetnik , I was able to find out the issue. After some research, I also found out that, it is actually easy to do this in C++17, we could either do:

std::array<unsigned char, 64> memory;
std::pmr::monotonic_buffer_resource pool{ memory.data(), memory.size() };

// then we can define our vector using stack memory
std::vector<int, std::pmr::polymorphic_allocator<int>> vec{&pool};

or, we can put the "std::array<unsigned char, 64> memory" inside a custom memory_resource.

In my case, I want to use in-place array for small objects to avoid 'allocations'. And for large objects, I want to use pooled memory.

So I inherited from _Identity_equal_resource and use a global unsynchronized_pool_resource behind the scene.

Now everything is just looking perfect to me.

// Note: still testing this.
template<size_t local_arena_size>
struct LocalArenaMemoryResource : public std::pmr::_Identity_equal_resource
{
private:
    static inline constexpr size_t max_blocks_per_chunk = 16;
    static inline constexpr size_t largest_required_pool_block = 16 * 1024;
    static inline std::pmr::unsynchronized_pool_resource _pool{ std::pmr::pool_options { max_blocks_per_chunk, largest_required_pool_block } };
private:
    using base = std::pmr::unsynchronized_pool_resource;
    void* do_allocate(size_t _Bytes, size_t _Align) override
    {
        if (_Bytes <= local_arena_size)
        {
            return static_cast<void*>(&m_local_buffer[0]);
        }

        return _pool.allocate(_Bytes, _Align);
    }

    void do_deallocate(void* _Ptr, size_t _Bytes, size_t _Align) override
    {
        if (_Ptr >= &m_local_buffer[0] && _Ptr < &m_local_buffer[local_arena_size - 1])
        {
            // do nothing
        }
        else
        {
            _pool.deallocate(_Ptr, _Bytes, _Align);
        }
    }

private:
    std::array<byte, local_arena_size> m_local_buffer;
};

There are things I'm still learning, such as: how unsynchronized_pool_resource handles the allocation when oversize alloc is required. Also I'm wondering if a fixed_block_pool will be more efficient. Still learning, testing, but it's really fun.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM