/** * \file libipc/allocator.h * \author mutouyun (orz@orzz.org) * \brief A generic polymorphic memory allocator. */ #pragma once #include #include #include // std::numeric_limits #include // std::forward #include // std::ignore #include #include "libipc/imp/export.h" #include "libipc/imp/uninitialized.h" #include "libipc/imp/byte.h" #include "libipc/mem/memory_resource.h" namespace ipc { namespace mem { /** * \brief An allocator which exhibits different allocation behavior * depending upon the memory resource from which it is constructed. * * \note Unlike `std::pmr::polymorphic_allocator`, it does not * rely on a specific inheritance relationship and only restricts * the interface behavior of the incoming memory resource object to * conform to `std::pmr::memory_resource`. * * \see https://en.cppreference.com/w/cpp/memory/memory_resource * https://en.cppreference.com/w/cpp/memory/polymorphic_allocator */ class LIBIPC_EXPORT allocator { class holder_mr_base { public: virtual ~holder_mr_base() noexcept = default; virtual void *alloc(std::size_t, std::size_t) const = 0; virtual void dealloc(void *, std::size_t, std::size_t) const = 0; }; template class holder_mr; /** * \brief An empty holding class used to calculate a reasonable memory size for the holder. * \tparam MR cannot be converted to the type of memory resource */ template class holder_mr : public holder_mr_base { protected: MR *res_; public: holder_mr(MR *p_mr) noexcept : res_(p_mr) {} // [MSVC] error C2259: 'pmr::allocator::holder_mr': cannot instantiate abstract class. void *alloc(std::size_t s, std::size_t a) const override { return nullptr; } void dealloc(void *p, std::size_t s, std::size_t a) const override {} }; /** * \brief A memory resource pointer holder class for type erasure. * \tparam MR memory resource type */ template class holder_mr> : public holder_mr { using base_t = holder_mr; public: holder_mr(MR *p_mr) noexcept : base_t{p_mr} {} void *alloc(std::size_t s, std::size_t a) const override { return base_t::res_->allocate(s, a); } void dealloc(void *p, std::size_t s, std::size_t a) const override { base_t::res_->deallocate(p, s, a); } }; using void_holder_t = holder_mr; alignas(void_holder_t) std::array holder_; holder_mr_base & get_holder() noexcept; holder_mr_base const &get_holder() const noexcept; public: /// \brief Constructs an `allocator` using the return value of /// `new_delete_resource::get()` as the underlying memory resource. allocator() noexcept; ~allocator() noexcept; allocator(allocator const &other) noexcept = default; allocator &operator=(allocator const &other) & noexcept = default; allocator(allocator &&other) noexcept = default; allocator &operator=(allocator &&other) & noexcept = default; /// \brief Constructs a allocator from a memory resource pointer. /// \note The lifetime of the pointer must be longer than that of allocator. template = true> allocator(T *p_mr) noexcept { if (p_mr == nullptr) { ipc::construct>(holder_.data(), new_delete_resource::get()); return; } ipc::construct>(holder_.data(), p_mr); } void swap(allocator &other) noexcept; /// \brief Allocate/deallocate memory. void *allocate(std::size_t s, std::size_t = alignof(std::max_align_t)) const; void deallocate(void *p, std::size_t s, std::size_t = alignof(std::max_align_t)) const; /// \brief Allocates uninitialized memory and constructs an object of type T in the memory. template T *construct(A &&...args) const { return ipc::construct(allocate(sizeof(T), alignof(T)), std::forward(args)...); } /// \brief Calls the destructor of the object pointed to by p and deallocates the memory. template void destroy(T *p) const noexcept { deallocate(ipc::destroy(p), sizeof(T), alignof(T)); } }; /** * \brief An allocator that can be used by all standard library containers, * based on ipc::allocator. * * \see https://en.cppreference.com/w/cpp/memory/allocator * https://en.cppreference.com/w/cpp/memory/polymorphic_allocator */ template class polymorphic_allocator { template friend class polymorphic_allocator; public: // type definitions typedef T value_type; typedef value_type * pointer; typedef const value_type *const_pointer; typedef value_type & reference; typedef const value_type &const_reference; typedef std::size_t size_type; typedef std::ptrdiff_t difference_type; private: allocator alloc_; public: // the other type of std_allocator template struct rebind { using other = polymorphic_allocator; }; polymorphic_allocator() noexcept {} // construct by copying (do nothing) polymorphic_allocator (polymorphic_allocator const &) noexcept {} polymorphic_allocator& operator=(polymorphic_allocator const &) noexcept { return *this; } // construct from a related allocator (do nothing) template polymorphic_allocator (polymorphic_allocator const &) noexcept {} template polymorphic_allocator &operator=(polymorphic_allocator const &) noexcept { return *this; } polymorphic_allocator (polymorphic_allocator &&) noexcept = default; polymorphic_allocator& operator=(polymorphic_allocator &&) noexcept = default; constexpr size_type max_size(void) const noexcept { return (std::numeric_limits::max)() / sizeof(value_type); } pointer allocate(size_type count) noexcept { if (count == 0) return nullptr; if (count > this->max_size()) return nullptr; return static_cast(alloc_.allocate(count * sizeof(value_type), alignof(T))); } void deallocate(pointer p, size_type count) noexcept { alloc_.deallocate(p, count * sizeof(value_type), alignof(T)); } template static void construct(pointer p, P && ... params) { std::ignore = ipc::construct(p, std::forward

(params)...); } static void destroy(pointer p) { std::ignore = ipc::destroy(p); } }; template constexpr bool operator==(polymorphic_allocator const &, polymorphic_allocator const &) noexcept { return true; } template constexpr bool operator!=(polymorphic_allocator const &, polymorphic_allocator const &) noexcept { return false; } } // namespace mem } // namespace ipc