Add allocator and rewrite allocator_wrapper

This commit is contained in:
mutouyun 2025-01-09 20:17:11 +08:00 committed by 木头云
parent 976610f914
commit d260897b16
6 changed files with 347 additions and 158 deletions

View File

@ -0,0 +1,215 @@
/**
* \file libipc/allocator.h
* \author mutouyun (orz@orzz.org)
* \brief A generic polymorphic memory allocator.
*/
#pragma once
#include <type_traits>
#include <array>
#include <limits> // std::numeric_limits
#include <utility> // std::forward
#include <tuple> // std::ignore
#include <cstddef>
#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 <typename MR, typename = bool>
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 <typename MR, typename U>
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<void *,bool>': 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 <typename MR>
class holder_mr<MR, verify_memory_resource<MR>> : public holder_mr<MR, void> {
using base_t = holder_mr<MR, void>;
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<void *>;
alignas(void_holder_t) std::array<ipc::byte, sizeof(void_holder_t)> 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 <typename T, verify_memory_resource<T> = true>
allocator(T *p_mr) noexcept {
if (p_mr == nullptr) {
ipc::construct<holder_mr<new_delete_resource>>(holder_.data(), new_delete_resource::get());
return;
}
ipc::construct<holder_mr<T>>(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 <typename T, typename... A>
T *construct(A &&...args) const {
return ipc::construct<T>(allocate(sizeof(T), alignof(T)), std::forward<A>(args)...);
}
/// \brief Calls the destructor of the object pointed to by p and deallocates the memory.
template <typename T>
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 <typename T>
class polymorphic_allocator {
template <typename U>
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 <typename U>
struct rebind {
using other = polymorphic_allocator<U>;
};
polymorphic_allocator() noexcept {}
// construct by copying (do nothing)
polymorphic_allocator (polymorphic_allocator<T> const &) noexcept {}
polymorphic_allocator& operator=(polymorphic_allocator<T> const &) noexcept { return *this; }
// construct from a related allocator (do nothing)
template <typename U> polymorphic_allocator (polymorphic_allocator<U> const &) noexcept {}
template <typename U> polymorphic_allocator &operator=(polymorphic_allocator<U> 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<size_type>::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<pointer>(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 <typename... P>
static void construct(pointer p, P && ... params) {
std::ignore = ipc::construct<T>(p, std::forward<P>(params)...);
}
static void destroy(pointer p) {
std::ignore = ipc::destroy(p);
}
};
template <typename T, typename U>
constexpr bool operator==(polymorphic_allocator<T> const &, polymorphic_allocator<U> const &) noexcept {
return true;
}
template <typename T, typename U>
constexpr bool operator!=(polymorphic_allocator<T> const &, polymorphic_allocator<U> const &) noexcept {
return false;
}
} // namespace mem
} // namespace ipc

View File

@ -0,0 +1,48 @@
#include <algorithm> // 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_mr_base *>(holder_.data());
}
allocator::holder_mr_base const &allocator::get_holder() const noexcept {
return *reinterpret_cast<holder_mr_base const *>(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

View File

@ -11,7 +11,6 @@
#include "libipc/rw_lock.h" #include "libipc/rw_lock.h"
#include "libipc/utility/concept.h" #include "libipc/utility/concept.h"
#include "libipc/memory/allocator_wrapper.h"
#include "libipc/platform/detail.h" #include "libipc/platform/detail.h"
namespace ipc { namespace ipc {

View File

@ -1,121 +0,0 @@
#pragma once
#include <limits> // std::numeric_limits
#include <utility> // std::forward
#include <cstddef>
#include "libipc/pool_alloc.h"
namespace ipc {
namespace mem {
////////////////////////////////////////////////////////////////
/// The allocator wrapper class for STL
////////////////////////////////////////////////////////////////
namespace detail {
template <typename T, typename AllocP>
struct rebind {
template <typename U>
using alloc_t = AllocP;
};
template <typename T, template <typename> class AllocT>
struct rebind<T, AllocT<T>> {
template <typename U>
using alloc_t = AllocT<U>;
};
} // namespace detail
template <typename T, typename AllocP>
class allocator_wrapper {
template <typename U, typename AllocU>
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<T, AllocP>&) noexcept {}
allocator_wrapper& operator=(const allocator_wrapper<T, AllocP>&) noexcept { return *this; }
// construct from a related allocator (do nothing)
template <typename U, typename AllocU> allocator_wrapper (const allocator_wrapper<U, AllocU>&) noexcept {}
template <typename U, typename AllocU> allocator_wrapper& operator=(const allocator_wrapper<U, AllocU>&) 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 <typename U>
struct rebind {
using other = allocator_wrapper< U, typename detail::rebind<T, AllocP>::template alloc_t<U> >;
};
constexpr size_type max_size(void) const noexcept {
return (std::numeric_limits<size_type>::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<pointer>(alloc_.alloc(count * sizeof(value_type)));
}
void deallocate(pointer p, size_type count) noexcept {
alloc_.free(p, count * sizeof(value_type));
}
template <typename... P>
static void construct(pointer p, P && ... params) {
ipc::mem::construct(p, std::forward<P>(params)...);
}
static void destroy(pointer p) {
ipc::mem::destruct(p);
}
};
template <class AllocP>
class allocator_wrapper<void, AllocP> {
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 <typename T, typename U, class AllocP>
constexpr bool operator==(const allocator_wrapper<T, AllocP>&, const allocator_wrapper<U, AllocP>&) noexcept {
return true;
}
template <typename T, typename U, class AllocP>
constexpr bool operator!=(const allocator_wrapper<T, AllocP>&, const allocator_wrapper<U, AllocP>&) noexcept {
return false;
}
} // namespace mem
} // namespace ipc

View File

@ -1,20 +1,13 @@
#pragma once #pragma once
#include <type_traits>
#include <limits>
#include <utility>
#include <functional>
#include <unordered_map> #include <unordered_map>
#include <map> #include <map>
#include <string> #include <string>
#include <cstdio>
#include "libipc/def.h" #include "libipc/def.h"
#include "libipc/memory/alloc.h" #include "libipc/memory/alloc.h"
#include "libipc/memory/wrapper.h"
#include "libipc/platform/detail.h"
#include "libipc/imp/fmt.h" #include "libipc/imp/fmt.h"
#include "libipc/mem/allocator.h"
namespace ipc { namespace ipc {
namespace mem { namespace mem {
@ -27,9 +20,6 @@ namespace mem {
// default_recycler >>>; // default_recycler >>>;
using async_pool_alloc = ipc::mem::static_alloc; using async_pool_alloc = ipc::mem::static_alloc;
template <typename T>
using allocator = allocator_wrapper<T, async_pool_alloc>;
} // namespace mem } // namespace mem
template <typename T> template <typename T>
@ -37,34 +27,14 @@ struct hash : public std::hash<T> {};
template <typename Key, typename T> template <typename Key, typename T>
using unordered_map = std::unordered_map< using unordered_map = std::unordered_map<
Key, T, ipc::hash<Key>, std::equal_to<Key>, ipc::mem::allocator<std::pair<Key const, T>> Key, T, ipc::hash<Key>, std::equal_to<Key>, ipc::mem::polymorphic_allocator<std::pair<Key const, T>>
>; >;
template <typename Key, typename T> template <typename Key, typename T>
using map = std::map< using map = std::map<
Key, T, std::less<Key>, ipc::mem::allocator<std::pair<Key const, T>> Key, T, std::less<Key>, ipc::mem::polymorphic_allocator<std::pair<Key const, T>>
>; >;
template <typename Char>
using basic_string = std::basic_string<
Char, std::char_traits<Char>, ipc::mem::allocator<Char>
>;
using string = basic_string<char>;
using wstring = basic_string<wchar_t>;
template <> struct hash<string> {
std::size_t operator()(string const &val) const noexcept {
return std::hash<char const *>{}(val.c_str());
}
};
template <> struct hash<wstring> {
std::size_t operator()(wstring const &val) const noexcept {
return std::hash<wchar_t const *>{}(val.c_str());
}
};
/// \brief Check string validity. /// \brief Check string validity.
constexpr bool is_valid_string(char const *str) noexcept { constexpr bool is_valid_string(char const *str) noexcept {
return (str != nullptr) && (str[0] != '\0'); return (str != nullptr) && (str[0] != '\0');

View File

@ -0,0 +1,78 @@
#include <vector>
#include <utility>
#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);
}