diff --git a/include/libipc/mem/allocator.h b/include/libipc/mem/allocator.h new file mode 100644 index 0000000..17ea01d --- /dev/null +++ b/include/libipc/mem/allocator.h @@ -0,0 +1,215 @@ +/** + * \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 diff --git a/src/libipc/mem/allocator.cpp b/src/libipc/mem/allocator.cpp new file mode 100644 index 0000000..187d22b --- /dev/null +++ b/src/libipc/mem/allocator.cpp @@ -0,0 +1,48 @@ + +#include // std::swap + +#include "libipc/imp/log.h" +#include "libipc/mem/allocator.h" + +namespace ipc { +namespace mem { + +allocator::holder_mr_base &allocator::get_holder() noexcept { + return *reinterpret_cast(holder_.data()); +} + +allocator::holder_mr_base const &allocator::get_holder() const noexcept { + return *reinterpret_cast(holder_.data()); +} + +allocator::allocator() noexcept + : allocator(new_delete_resource::get()) {} + +allocator::~allocator() noexcept { + ipc::destroy(&get_holder()); +} + +void allocator::swap(allocator &other) noexcept { + std::swap(this->holder_, other.holder_); +} + +void *allocator::allocate(std::size_t s, std::size_t a) const { + LIBIPC_LOG(); + if ((a & (a - 1)) != 0) { + log.error("failed: allocate alignment is not a power of 2."); + return nullptr; + } + return get_holder().alloc(s, a); +} + +void allocator::deallocate(void *p, std::size_t s, std::size_t a) const { + LIBIPC_LOG(); + if ((a & (a - 1)) != 0) { + log.error("failed: allocate alignment is not a power of 2."); + return; + } + get_holder().dealloc(p, s, a); +} + +} // namespace mem +} // namespace ipc diff --git a/src/libipc/memory/alloc.h b/src/libipc/memory/alloc.h index 3a4ce20..29cbc2e 100755 --- a/src/libipc/memory/alloc.h +++ b/src/libipc/memory/alloc.h @@ -11,7 +11,6 @@ #include "libipc/rw_lock.h" #include "libipc/utility/concept.h" -#include "libipc/memory/allocator_wrapper.h" #include "libipc/platform/detail.h" namespace ipc { diff --git a/src/libipc/memory/allocator_wrapper.h b/src/libipc/memory/allocator_wrapper.h deleted file mode 100755 index d1c3ea3..0000000 --- a/src/libipc/memory/allocator_wrapper.h +++ /dev/null @@ -1,121 +0,0 @@ -#pragma once - -#include // std::numeric_limits -#include // std::forward -#include - -#include "libipc/pool_alloc.h" - -namespace ipc { -namespace mem { - -//////////////////////////////////////////////////////////////// -/// The allocator wrapper class for STL -//////////////////////////////////////////////////////////////// - -namespace detail { - -template -struct rebind { - template - using alloc_t = AllocP; -}; - -template class AllocT> -struct rebind> { - template - using alloc_t = AllocT; -}; - -} // namespace detail - -template -class allocator_wrapper { - - template - friend class allocator_wrapper; - -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; - typedef AllocP alloc_policy; - -private: - alloc_policy alloc_; - -public: - allocator_wrapper() noexcept {} - - // construct by copying (do nothing) - allocator_wrapper (const allocator_wrapper&) noexcept {} - allocator_wrapper& operator=(const allocator_wrapper&) noexcept { return *this; } - - // construct from a related allocator (do nothing) - template allocator_wrapper (const allocator_wrapper&) noexcept {} - template allocator_wrapper& operator=(const allocator_wrapper&) noexcept { return *this; } - - allocator_wrapper (allocator_wrapper && rhs) noexcept : alloc_ ( std::move(rhs.alloc_) ) {} - allocator_wrapper& operator=(allocator_wrapper && rhs) noexcept { alloc_ = std::move(rhs.alloc_); return *this; } - -public: - // the other type of std_allocator - template - struct rebind { - using other = allocator_wrapper< U, typename detail::rebind::template alloc_t >; - }; - - constexpr size_type max_size(void) const noexcept { - return (std::numeric_limits::max)() / sizeof(value_type); - } - -public: - pointer allocate(size_type count) noexcept { - if (count == 0) return nullptr; - if (count > this->max_size()) return nullptr; - return static_cast(alloc_.alloc(count * sizeof(value_type))); - } - - void deallocate(pointer p, size_type count) noexcept { - alloc_.free(p, count * sizeof(value_type)); - } - - template - static void construct(pointer p, P && ... params) { - ipc::mem::construct(p, std::forward

(params)...); - } - - static void destroy(pointer p) { - ipc::mem::destruct(p); - } -}; - -template -class allocator_wrapper { -public: - // type definitions - typedef void value_type; - typedef value_type* pointer; - typedef const value_type* const_pointer; - typedef std::size_t size_type; - typedef std::ptrdiff_t difference_type; - typedef AllocP alloc_policy; -}; - -template -constexpr bool operator==(const allocator_wrapper&, const allocator_wrapper&) noexcept { - return true; -} - -template -constexpr bool operator!=(const allocator_wrapper&, const allocator_wrapper&) noexcept { - return false; -} - -} // namespace mem -} // namespace ipc diff --git a/src/libipc/memory/resource.h b/src/libipc/memory/resource.h index 7f234be..5eea278 100755 --- a/src/libipc/memory/resource.h +++ b/src/libipc/memory/resource.h @@ -1,20 +1,13 @@ #pragma once -#include -#include -#include -#include #include #include #include -#include #include "libipc/def.h" - #include "libipc/memory/alloc.h" -#include "libipc/memory/wrapper.h" -#include "libipc/platform/detail.h" #include "libipc/imp/fmt.h" +#include "libipc/mem/allocator.h" namespace ipc { namespace mem { @@ -27,9 +20,6 @@ namespace mem { // default_recycler >>>; using async_pool_alloc = ipc::mem::static_alloc; -template -using allocator = allocator_wrapper; - } // namespace mem template @@ -37,48 +27,28 @@ struct hash : public std::hash {}; template using unordered_map = std::unordered_map< - Key, T, ipc::hash, std::equal_to, ipc::mem::allocator> + Key, T, ipc::hash, std::equal_to, ipc::mem::polymorphic_allocator> >; template using map = std::map< - Key, T, std::less, ipc::mem::allocator> + Key, T, std::less, ipc::mem::polymorphic_allocator> >; -template -using basic_string = std::basic_string< - Char, std::char_traits, ipc::mem::allocator ->; - -using string = basic_string; -using wstring = basic_string; - -template <> struct hash { - std::size_t operator()(string const &val) const noexcept { - return std::hash{}(val.c_str()); - } -}; - -template <> struct hash { - std::size_t operator()(wstring const &val) const noexcept { - return std::hash{}(val.c_str()); - } -}; - /// \brief Check string validity. constexpr bool is_valid_string(char const *str) noexcept { - return (str != nullptr) && (str[0] != '\0'); + return (str != nullptr) && (str[0] != '\0'); } /// \brief Make a valid string. inline std::string make_string(char const *str) { - return is_valid_string(str) ? std::string{str} : std::string{}; + return is_valid_string(str) ? std::string{str} : std::string{}; } /// \brief Combine prefix from a list of strings. template inline std::string make_prefix(std::string prefix, A &&...args) { - return ipc::fmt(prefix, "__IPC_SHM__", std::forward(args)...); + return ipc::fmt(prefix, "__IPC_SHM__", std::forward(args)...); } } // namespace ipc diff --git a/test/mem/test_mem_allocator.cpp b/test/mem/test_mem_allocator.cpp new file mode 100644 index 0000000..389b959 --- /dev/null +++ b/test/mem/test_mem_allocator.cpp @@ -0,0 +1,78 @@ + +#include +#include + +#include "test.h" + +#include "libipc/mem/allocator.h" + +TEST(allocator, construct) { + ipc::mem::allocator alc; + SUCCEED(); +} + +TEST(allocator, construct_value_initialization) { + ipc::mem::allocator alc{}; + auto p = alc.allocate(128); + EXPECT_NE(p, nullptr); + EXPECT_NO_THROW(alc.deallocate(p, 128)); +} + +namespace { + +class dummy_resource { +public: + void *allocate(std::size_t, std::size_t = 0) noexcept { + return nullptr; + } + void deallocate(void *, std::size_t, std::size_t = 0) noexcept { + return; + } +}; + +} // namespace + +TEST(allocator, construct_copy_move) { + ipc::mem::new_delete_resource mem_res; + dummy_resource dummy_res; + ipc::mem::allocator alc1{&mem_res}, alc2{&dummy_res}; + auto p = alc1.allocate(128); + ASSERT_NE(p, nullptr); + ASSERT_NO_THROW(alc1.deallocate(p, 128)); + ASSERT_EQ(alc2.allocate(128), nullptr); + + ipc::mem::allocator alc3{alc1}, alc4{alc2}, alc5{std::move(alc1)}; + + p = alc3.allocate(128); + ASSERT_NE(p, nullptr); + ASSERT_NO_THROW(alc3.deallocate(p, 128)); + + ASSERT_EQ(alc4.allocate(128), nullptr); + + p = alc5.allocate(128); + ASSERT_NE(p, nullptr); + ASSERT_NO_THROW(alc5.deallocate(p, 128)); +} + +TEST(allocator, swap) { + ipc::mem::new_delete_resource mem_res; + dummy_resource dummy_res; + ipc::mem::allocator alc1{&mem_res}, alc2{&dummy_res}; + alc1.swap(alc2); + auto p = alc2.allocate(128); + ASSERT_NE(p, nullptr); + ASSERT_NO_THROW(alc2.deallocate(p, 128)); + ASSERT_EQ(alc1.allocate(128), nullptr); +} + +TEST(allocator, invalid_alloc_free) { + ipc::mem::allocator alc1; + EXPECT_EQ(alc1.allocate(0), nullptr); + EXPECT_NO_THROW(alc1.deallocate(nullptr, 128)); + EXPECT_NO_THROW(alc1.deallocate(nullptr, 0)); + EXPECT_NO_THROW(alc1.deallocate(&alc1, 0)); +} + +TEST(allocator, sizeof) { + EXPECT_EQ(sizeof(ipc::mem::allocator), sizeof(void *) * 2); +}