diff --git a/include/libpmr/allocator.h b/include/libpmr/allocator.h index c9a279f..c021d21 100644 --- a/include/libpmr/allocator.h +++ b/include/libpmr/allocator.h @@ -7,41 +7,16 @@ #pragma once #include +#include #include "libimp/export.h" +#include "libimp/construct.h" +#include "libimp/byte.h" #include "libpmr/def.h" #include "libpmr/memory_resource.h" LIBPMR_NAMESPACE_BEG_ -namespace detail { - -/// @brief Helper trait for allocator. - -template -struct has_allocate : std::false_type {}; - -template -struct has_allocate().allocate(std::declval())), void * - >::value>::type> : std::true_type {}; - -template -struct has_deallocate : std::false_type {}; - -template -struct has_deallocate().deallocate(std::declval(), - std::declval())) - > : std::true_type {}; - -template -using is_memory_resource = - typename std::enable_if::value && - has_deallocate::value>::type; - -} // namespace detail /** * @brief An allocator which exhibits different allocation behavior @@ -57,6 +32,78 @@ using is_memory_resource = */ class LIBIMP_EXPORT allocator { + class holder_base { + public: + virtual ~holder_base() noexcept = default; + virtual void *alloc(std::size_t) = 0; + virtual void free (void *, std::size_t) = 0; + virtual bool valid() const noexcept = 0; + }; + + class holder_null : public holder_base { + public: + void *alloc(std::size_t) override { return nullptr; } + void free (void *, std::size_t) override {} + bool valid() const noexcept override { return false; } + }; + + template + class holder_memory_resource : public holder_base { + MemRes *p_mem_res_; + + public: + holder_memory_resource(MemRes *p_mr) noexcept + : p_mem_res_(p_mr) {} + + void *alloc(std::size_t s) override { + return p_mem_res_->allocate(s); + } + + void free(void *p, std::size_t s) override { + p_mem_res_->deallocate(p, s); + } + + bool valid() const noexcept override { + return p_mem_res_ != nullptr; + } + }; + + template <> + class holder_memory_resource : public holder_null { + void *p_dummy_; + }; + + using void_holder_type = holder_memory_resource; + alignas(void_holder_type) std::array<::LIBIMP_::byte, sizeof(void_holder_type)> holder_; + + holder_base & get_holder() noexcept; + holder_base const &get_holder() const noexcept; + +public: + allocator() noexcept; + ~allocator() noexcept; + + allocator(allocator const &other) noexcept = default; + allocator &operator=(allocator const &other) & noexcept = default; + + allocator(allocator &&other) noexcept; + allocator &operator=(allocator &&other) & noexcept; + + /// @brief Constructs a allocator from a memory resource pointer + /// @remark The lifetime of the pointer must be longer than that of allocator. + template > + allocator(T *p_mr) : allocator() { + if (p_mr == nullptr) return; + ::LIBIMP_::construct>(holder_.data(), p_mr); + } + + void swap(allocator &other) noexcept; + bool valid() const noexcept; + explicit operator bool() const noexcept; + + /// @brief Allocate/deallocate memory. + void *alloc(std::size_t s); + void free (void *p, std::size_t s); }; LIBPMR_NAMESPACE_END_ diff --git a/include/libpmr/memory_resource.h b/include/libpmr/memory_resource.h index fb05132..7baf5fe 100644 --- a/include/libpmr/memory_resource.h +++ b/include/libpmr/memory_resource.h @@ -6,6 +6,7 @@ */ #pragma once +#include #include // std::size_t, std::max_align_t #include "libimp/export.h" @@ -13,14 +14,42 @@ LIBPMR_NAMESPACE_BEG_ +/// @brief Helper trait for memory resource. + +template +struct has_allocate : std::false_type {}; + +template +struct has_allocate().allocate(std::declval())), void * + >::value>::type> : std::true_type {}; + +template +struct has_deallocate : std::false_type {}; + +template +struct has_deallocate().deallocate(std::declval(), + std::declval())) + > : std::true_type {}; + +template +using is_memory_resource = + typename std::enable_if::value && + has_deallocate::value>::type; + /** * @brief A memory resource that uses the - * global operator new and operator delete to allocate memory. + * standard memory allocation and deallocation interface to allocate memory. * * @see https://en.cppreference.com/w/cpp/memory/new_delete_resource */ class LIBIMP_EXPORT new_delete_resource { public: + /// @brief Returns a pointer to a new_delete_resource. + static new_delete_resource *get() noexcept; + /// @brief Allocates storage with a size of at least bytes bytes, aligned to the specified alignment. /// @remark Returns nullptr if storage of the requested size and alignment cannot be obtained. /// @see https://en.cppreference.com/w/cpp/memory/memory_resource/do_allocate diff --git a/src/libpmr/allocator.cpp b/src/libpmr/allocator.cpp new file mode 100644 index 0000000..e9c15db --- /dev/null +++ b/src/libpmr/allocator.cpp @@ -0,0 +1,58 @@ + +#include // std::swap + +#include "libpmr/allocator.h" + +LIBPMR_NAMESPACE_BEG_ + +allocator::holder_base &allocator::get_holder() noexcept { + return *::LIBIMP_::byte_cast(holder_.data()); +} + +allocator::holder_base const &allocator::get_holder() const noexcept { + return *::LIBIMP_::byte_cast(holder_.data()); +} + +allocator::allocator() noexcept { + ::LIBIMP_::construct(holder_.data()); +} + +allocator::~allocator() noexcept { + ::LIBIMP_::destroy(&get_holder()); +} + +allocator::allocator(allocator &&other) noexcept + : allocator(other) /*copy*/ { + ::LIBIMP_::construct(other.holder_.data()); +} + +allocator &allocator::operator=(allocator &&other) & noexcept { + if (this == &other) return *this; + this->holder_ = other.holder_; + ::LIBIMP_::construct(other.holder_.data()); + return *this; +} + +void allocator::swap(allocator &other) noexcept { + std::swap(this->holder_, other.holder_); +} + +bool allocator::valid() const noexcept { + return get_holder().valid(); +} + +allocator::operator bool() const noexcept { + return valid(); +} + +void *allocator::alloc(std::size_t s) { + if (!valid()) return nullptr; + return get_holder().alloc(s); +} + +void allocator::free(void *p, std::size_t s) { + if (!valid()) return; + get_holder().free(p, s); +} + +LIBPMR_NAMESPACE_END_ diff --git a/src/libpmr/memory_resource.cpp b/src/libpmr/memory_resource.cpp index ef9fe95..e7d86a5 100644 --- a/src/libpmr/memory_resource.cpp +++ b/src/libpmr/memory_resource.cpp @@ -27,6 +27,16 @@ bool verify_args(::LIBIMP_::log::gripper &log, std::size_t bytes, std::size_t al } // namespace +/** + * @brief Returns a pointer to a new_delete_resource. + * + * @return new_delete_resource* + */ +new_delete_resource *new_delete_resource::get() noexcept { + static new_delete_resource mem_res; + return &mem_res; +} + /** * @brief Allocates storage with a size of at least bytes bytes, aligned to the specified alignment. * @remark Alignment shall be a power of two. diff --git a/test/pmr/test_pmr_allocator.cpp b/test/pmr/test_pmr_allocator.cpp index 1f3be4f..2860979 100644 --- a/test/pmr/test_pmr_allocator.cpp +++ b/test/pmr/test_pmr_allocator.cpp @@ -1,32 +1,78 @@ #include -#if defined(LIBIMP_CPP_17) && defined(__cpp_lib_memory_resource) -#include -#endif +#include #include "gtest/gtest.h" #include "libpmr/allocator.h" -TEST(allocator, detail) { - EXPECT_FALSE(pmr::detail::has_allocate::value); - EXPECT_FALSE(pmr::detail::has_allocate::value); - EXPECT_FALSE(pmr::detail::has_allocate>::value); - EXPECT_TRUE (pmr::detail::has_allocate>::value); -#if defined(LIBIMP_CPP_17) && defined(__cpp_lib_memory_resource) - EXPECT_TRUE (pmr::detail::has_allocate::value); - EXPECT_TRUE (pmr::detail::has_allocate>::value); -#endif - - EXPECT_FALSE(pmr::detail::has_deallocate::value); - EXPECT_FALSE(pmr::detail::has_deallocate::value); - EXPECT_FALSE(pmr::detail::has_deallocate>::value); - EXPECT_FALSE(pmr::detail::has_deallocate>::value); -#if defined(LIBIMP_CPP_17) && defined(__cpp_lib_memory_resource) - EXPECT_TRUE (pmr::detail::has_deallocate::value); - EXPECT_FALSE(pmr::detail::has_deallocate>::value); -#endif +TEST(allocator, construct) { + pmr::allocator alc; + EXPECT_FALSE(alc.valid()); + EXPECT_FALSE(alc); } -TEST(allocator, construct) { +TEST(allocator, construct_with_memory_resource) { + pmr::new_delete_resource mem_res; + pmr::allocator alc {&mem_res}; + EXPECT_TRUE(alc.valid()); + EXPECT_TRUE(alc); + + auto p = alc.alloc(128); + EXPECT_NE(p, nullptr); + EXPECT_NO_THROW(alc.free(p, 128)); +} + +TEST(allocator, construct_copy_move) { + pmr::new_delete_resource mem_res; + + pmr::allocator alc1 {&mem_res}, alc2; + EXPECT_TRUE (alc1.valid()); + EXPECT_TRUE (alc1); + EXPECT_FALSE(alc2.valid()); + EXPECT_FALSE(alc2); + + pmr::allocator alc3 {alc1}, alc4{alc2}, alc5 {std::move(alc1)}; + EXPECT_TRUE (alc3.valid()); + EXPECT_TRUE (alc3); + EXPECT_FALSE(alc4.valid()); + EXPECT_FALSE(alc4); + EXPECT_TRUE (alc5.valid()); + EXPECT_TRUE (alc5); + EXPECT_FALSE(alc1.valid()); + EXPECT_FALSE(alc1); +} + +TEST(allocator, swap) { + pmr::new_delete_resource mem_res; + + pmr::allocator alc1 {&mem_res}, alc2; + EXPECT_TRUE (alc1.valid()); + EXPECT_TRUE (alc1); + EXPECT_FALSE(alc2.valid()); + EXPECT_FALSE(alc2); + + alc1.swap(alc2); + EXPECT_FALSE(alc1.valid()); + EXPECT_FALSE(alc1); + EXPECT_TRUE (alc2.valid()); + EXPECT_TRUE (alc2); +} + +TEST(allocator, invalid_alloc_free) { + pmr::new_delete_resource mem_res; + + pmr::allocator alc1 {&mem_res}, alc2; + EXPECT_EQ(alc1.alloc(0), nullptr); + EXPECT_NO_THROW(alc1.free(nullptr, 128)); + EXPECT_NO_THROW(alc1.free(nullptr, 0)); + EXPECT_NO_THROW(alc1.free(&alc1, 0)); + + EXPECT_EQ(alc2.alloc(0), nullptr); + EXPECT_NO_THROW(alc2.free(nullptr, 128)); + EXPECT_NO_THROW(alc2.free(nullptr, 0)); + EXPECT_NO_THROW(alc2.free(&alc1, 0)); + + EXPECT_EQ(alc2.alloc(1024), nullptr); + EXPECT_NO_THROW(alc2.free(&alc1, sizeof(alc1))); } \ No newline at end of file diff --git a/test/pmr/test_pmr_memory_resource.cpp b/test/pmr/test_pmr_memory_resource.cpp index 5c662aa..f90e4d3 100644 --- a/test/pmr/test_pmr_memory_resource.cpp +++ b/test/pmr/test_pmr_memory_resource.cpp @@ -1,5 +1,8 @@ #include +#if defined(LIBIMP_CPP_17) && defined(__cpp_lib_memory_resource) +#include +#endif #include "gtest/gtest.h" @@ -21,20 +24,40 @@ void *test_mr(T &&mr, std::size_t bytes, std::size_t alignment) { } // namespace +TEST(memory_resource, traits) { + EXPECT_FALSE(pmr::has_allocate::value); + EXPECT_FALSE(pmr::has_allocate::value); + EXPECT_FALSE(pmr::has_allocate>::value); + EXPECT_TRUE (pmr::has_allocate>::value); +#if defined(LIBIMP_CPP_17) && defined(__cpp_lib_memory_resource) + EXPECT_TRUE (pmr::has_allocate::value); + EXPECT_TRUE (pmr::has_allocate>::value); +#endif + + EXPECT_FALSE(pmr::has_deallocate::value); + EXPECT_FALSE(pmr::has_deallocate::value); + EXPECT_FALSE(pmr::has_deallocate>::value); + EXPECT_FALSE(pmr::has_deallocate>::value); +#if defined(LIBIMP_CPP_17) && defined(__cpp_lib_memory_resource) + EXPECT_TRUE (pmr::has_deallocate::value); + EXPECT_FALSE(pmr::has_deallocate>::value); +#endif +} + TEST(memory_resource, new_delete_resource) { - pmr::new_delete_resource m_res; + pmr::new_delete_resource mem_res; - EXPECT_EQ(test_mr(m_res, 0, 0), nullptr); - EXPECT_EQ(test_mr(m_res, 0, 1), nullptr); - EXPECT_EQ(test_mr(m_res, 0, 2), nullptr); - EXPECT_EQ(test_mr(m_res, 0, 3), nullptr); - EXPECT_EQ(test_mr(m_res, 0, 8), nullptr); - EXPECT_EQ(test_mr(m_res, 0, 64), nullptr); + EXPECT_EQ(test_mr(mem_res, 0, 0), nullptr); + EXPECT_EQ(test_mr(mem_res, 0, 1), nullptr); + EXPECT_EQ(test_mr(mem_res, 0, 2), nullptr); + EXPECT_EQ(test_mr(mem_res, 0, 3), nullptr); + EXPECT_EQ(test_mr(mem_res, 0, 8), nullptr); + EXPECT_EQ(test_mr(mem_res, 0, 64), nullptr); - EXPECT_EQ(test_mr(m_res, 1, 0), nullptr); - EXPECT_NE(test_mr(m_res, 1, 1), nullptr); - EXPECT_NE(test_mr(m_res, 1, 2), nullptr); - EXPECT_EQ(test_mr(m_res, 1, 3), nullptr); - EXPECT_NE(test_mr(m_res, 1, 8), nullptr); - EXPECT_NE(test_mr(m_res, 1, 64), nullptr); + EXPECT_EQ(test_mr(mem_res, 1, 0), nullptr); + EXPECT_NE(test_mr(mem_res, 1, 1), nullptr); + EXPECT_NE(test_mr(mem_res, 1, 2), nullptr); + EXPECT_EQ(test_mr(mem_res, 1, 3), nullptr); + EXPECT_NE(test_mr(mem_res, 1, 8), nullptr); + EXPECT_NE(test_mr(mem_res, 1, 64), nullptr); } \ No newline at end of file