1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163
|
/**
* \file libipc/bytes_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"
namespace ipc {
namespace mem {
/// \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>(),
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::declval<std::size_t>()))
> : std::true_type {};
template <typename T>
using is_memory_resource =
std::enable_if_t<has_allocate <T>::value &&
has_deallocate<T>::value, bool>;
/**
* \brief An allocator which exhibits different allocation behavior
* depending upon the memory resource from which it is constructed.
*
* \note Unlike `std::pmr::container_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/container_allocator
*/
class LIBIPC_EXPORT bytes_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: 'bytes_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, is_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;
void init_default_resource() noexcept;
public:
/// \brief Constructs an `bytes_allocator` using the return value of
/// `new_delete_resource::get()` as the underlying memory resource.
bytes_allocator() noexcept;
~bytes_allocator() noexcept;
bytes_allocator(bytes_allocator const &other) noexcept = default;
bytes_allocator &operator=(bytes_allocator const &other) & noexcept = default;
bytes_allocator(bytes_allocator &&other) noexcept = default;
bytes_allocator &operator=(bytes_allocator &&other) & noexcept = default;
/// \brief Constructs a `bytes_allocator` from a memory resource pointer.
/// \note The lifetime of the pointer must be longer than that of bytes_allocator.
template <typename T, is_memory_resource<T> = true>
bytes_allocator(T *p_mr) noexcept {
if (p_mr == nullptr) {
init_default_resource();
return;
}
std::ignore = ipc::construct<holder_mr<T>>(holder_.data(), p_mr);
}
void swap(bytes_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));
}
};
} // namespace mem
} // namespace ipc
|