mirror of
https://github.com/mutouyun/cpp-ipc.git
synced 2025-12-06 08:46:45 +08:00
Add allocator and rewrite allocator_wrapper
This commit is contained in:
parent
976610f914
commit
d260897b16
215
include/libipc/mem/allocator.h
Normal file
215
include/libipc/mem/allocator.h
Normal 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
|
||||
48
src/libipc/mem/allocator.cpp
Normal file
48
src/libipc/mem/allocator.cpp
Normal 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
|
||||
@ -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 {
|
||||
|
||||
@ -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
|
||||
@ -1,20 +1,13 @@
|
||||
#pragma once
|
||||
|
||||
#include <type_traits>
|
||||
#include <limits>
|
||||
#include <utility>
|
||||
#include <functional>
|
||||
#include <unordered_map>
|
||||
#include <map>
|
||||
#include <string>
|
||||
#include <cstdio>
|
||||
|
||||
#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 <typename T>
|
||||
using allocator = allocator_wrapper<T, async_pool_alloc>;
|
||||
|
||||
} // namespace mem
|
||||
|
||||
template <typename T>
|
||||
@ -37,48 +27,28 @@ struct hash : public std::hash<T> {};
|
||||
|
||||
template <typename Key, typename T>
|
||||
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>
|
||||
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.
|
||||
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 <typename... A>
|
||||
inline std::string make_prefix(std::string prefix, A &&...args) {
|
||||
return ipc::fmt(prefix, "__IPC_SHM__", std::forward<A>(args)...);
|
||||
return ipc::fmt(prefix, "__IPC_SHM__", std::forward<A>(args)...);
|
||||
}
|
||||
|
||||
} // namespace ipc
|
||||
|
||||
78
test/mem/test_mem_allocator.cpp
Normal file
78
test/mem/test_mem_allocator.cpp
Normal 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);
|
||||
}
|
||||
Loading…
x
Reference in New Issue
Block a user