add: [pmr] allocator & ut

This commit is contained in:
mutouyun 2022-11-18 21:25:14 +08:00
parent cc17980b97
commit 1fd5b3825e
6 changed files with 277 additions and 64 deletions

View File

@ -7,41 +7,16 @@
#pragma once
#include <type_traits>
#include <array>
#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 <typename T, typename = void>
struct has_allocate : std::false_type {};
template <typename T>
struct has_allocate<T,
typename std::enable_if<std::is_convertible<
decltype(std::declval<T &>().allocate(std::declval<std::size_t>())), void *
>::value>::type> : std::true_type {};
template <typename T, typename = void>
struct has_deallocate : std::false_type {};
template <typename T>
struct has_deallocate<T,
decltype(std::declval<T &>().deallocate(std::declval<void *>(),
std::declval<std::size_t>()))
> : std::true_type {};
template <typename T>
using is_memory_resource =
typename std::enable_if<has_allocate <T>::value &&
has_deallocate<T>::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 <typename MemRes>
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<void> : public holder_null {
void *p_dummy_;
};
using void_holder_type = holder_memory_resource<void>;
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 <typename T, typename = is_memory_resource<T>>
allocator(T *p_mr) : allocator() {
if (p_mr == nullptr) return;
::LIBIMP_::construct<holder_memory_resource<T>>(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_

View File

@ -6,6 +6,7 @@
*/
#pragma once
#include <type_traits>
#include <cstddef> // 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 <typename T, typename = void>
struct has_allocate : std::false_type {};
template <typename T>
struct has_allocate<T,
typename std::enable_if<std::is_convertible<
decltype(std::declval<T &>().allocate(std::declval<std::size_t>())), void *
>::value>::type> : std::true_type {};
template <typename T, typename = void>
struct has_deallocate : std::false_type {};
template <typename T>
struct has_deallocate<T,
decltype(std::declval<T &>().deallocate(std::declval<void *>(),
std::declval<std::size_t>()))
> : std::true_type {};
template <typename T>
using is_memory_resource =
typename std::enable_if<has_allocate <T>::value &&
has_deallocate<T>::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

58
src/libpmr/allocator.cpp Normal file
View File

@ -0,0 +1,58 @@
#include <algorithm> // std::swap
#include "libpmr/allocator.h"
LIBPMR_NAMESPACE_BEG_
allocator::holder_base &allocator::get_holder() noexcept {
return *::LIBIMP_::byte_cast<holder_base>(holder_.data());
}
allocator::holder_base const &allocator::get_holder() const noexcept {
return *::LIBIMP_::byte_cast<holder_base const>(holder_.data());
}
allocator::allocator() noexcept {
::LIBIMP_::construct<holder_null>(holder_.data());
}
allocator::~allocator() noexcept {
::LIBIMP_::destroy(&get_holder());
}
allocator::allocator(allocator &&other) noexcept
: allocator(other) /*copy*/ {
::LIBIMP_::construct<holder_null>(other.holder_.data());
}
allocator &allocator::operator=(allocator &&other) & noexcept {
if (this == &other) return *this;
this->holder_ = other.holder_;
::LIBIMP_::construct<holder_null>(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_

View File

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

View File

@ -1,32 +1,78 @@
#include <vector>
#if defined(LIBIMP_CPP_17) && defined(__cpp_lib_memory_resource)
#include <memory_resource>
#endif
#include <utility>
#include "gtest/gtest.h"
#include "libpmr/allocator.h"
TEST(allocator, detail) {
EXPECT_FALSE(pmr::detail::has_allocate<void>::value);
EXPECT_FALSE(pmr::detail::has_allocate<int>::value);
EXPECT_FALSE(pmr::detail::has_allocate<std::vector<int>>::value);
EXPECT_TRUE (pmr::detail::has_allocate<std::allocator<int>>::value);
#if defined(LIBIMP_CPP_17) && defined(__cpp_lib_memory_resource)
EXPECT_TRUE (pmr::detail::has_allocate<std::pmr::memory_resource>::value);
EXPECT_TRUE (pmr::detail::has_allocate<std::pmr::polymorphic_allocator<int>>::value);
#endif
EXPECT_FALSE(pmr::detail::has_deallocate<void>::value);
EXPECT_FALSE(pmr::detail::has_deallocate<int>::value);
EXPECT_FALSE(pmr::detail::has_deallocate<std::vector<int>>::value);
EXPECT_FALSE(pmr::detail::has_deallocate<std::allocator<int>>::value);
#if defined(LIBIMP_CPP_17) && defined(__cpp_lib_memory_resource)
EXPECT_TRUE (pmr::detail::has_deallocate<std::pmr::memory_resource>::value);
EXPECT_FALSE(pmr::detail::has_deallocate<std::pmr::polymorphic_allocator<int>>::value);
#endif
}
TEST(allocator, construct) {
pmr::allocator alc;
EXPECT_FALSE(alc.valid());
EXPECT_FALSE(alc);
}
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)));
}

View File

@ -1,5 +1,8 @@
#include <utility>
#if defined(LIBIMP_CPP_17) && defined(__cpp_lib_memory_resource)
#include <memory_resource>
#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, new_delete_resource) {
pmr::new_delete_resource m_res;
TEST(memory_resource, traits) {
EXPECT_FALSE(pmr::has_allocate<void>::value);
EXPECT_FALSE(pmr::has_allocate<int>::value);
EXPECT_FALSE(pmr::has_allocate<std::vector<int>>::value);
EXPECT_TRUE (pmr::has_allocate<std::allocator<int>>::value);
#if defined(LIBIMP_CPP_17) && defined(__cpp_lib_memory_resource)
EXPECT_TRUE (pmr::has_allocate<std::pmr::memory_resource>::value);
EXPECT_TRUE (pmr::has_allocate<std::pmr::polymorphic_allocator<int>>::value);
#endif
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(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_FALSE(pmr::has_deallocate<void>::value);
EXPECT_FALSE(pmr::has_deallocate<int>::value);
EXPECT_FALSE(pmr::has_deallocate<std::vector<int>>::value);
EXPECT_FALSE(pmr::has_deallocate<std::allocator<int>>::value);
#if defined(LIBIMP_CPP_17) && defined(__cpp_lib_memory_resource)
EXPECT_TRUE (pmr::has_deallocate<std::pmr::memory_resource>::value);
EXPECT_FALSE(pmr::has_deallocate<std::pmr::polymorphic_allocator<int>>::value);
#endif
}
TEST(memory_resource, new_delete_resource) {
pmr::new_delete_resource mem_res;
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(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);
}