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
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.