diff --git a/include/libconcur/data_model.h b/include/libconcur/data_model.h index 514b26d..e6865d4 100644 --- a/include/libconcur/data_model.h +++ b/include/libconcur/data_model.h @@ -11,7 +11,7 @@ #include #include -#include "libimp/construct.h" +#include "libimp/uninitialized.h" #include "libimp/detect_plat.h" #include "libimp/aligned.h" diff --git a/include/libimp/expected.h b/include/libimp/expected.h index 533e355..af2ee7d 100644 --- a/include/libimp/expected.h +++ b/include/libimp/expected.h @@ -13,7 +13,7 @@ #include // std::nullptr_t #include "libimp/def.h" -#include "libimp/construct.h" +#include "libimp/uninitialized.h" #include "libimp/generic.h" #include "libimp/byte.h" diff --git a/include/libimp/generic.h b/include/libimp/generic.h index e030181..2106adc 100644 --- a/include/libimp/generic.h +++ b/include/libimp/generic.h @@ -21,6 +21,12 @@ LIBIMP_NAMESPACE_BEG_ template using void_t = void; +/** + * \brief A type-list for generic programming. +*/ +template +struct types {}; + /** * \brief To indicate that the contained object should be constructed in-place. * \see https://en.cppreference.com/w/cpp/utility/in_place diff --git a/include/libimp/pimpl.h b/include/libimp/pimpl.h index df7787a..d0b0b2b 100644 --- a/include/libimp/pimpl.h +++ b/include/libimp/pimpl.h @@ -10,7 +10,7 @@ #include #include -#include "libimp/construct.h" +#include "libimp/uninitialized.h" #include "libimp/def.h" LIBIMP_NAMESPACE_BEG_ diff --git a/include/libimp/construct.h b/include/libimp/uninitialized.h similarity index 58% rename from include/libimp/construct.h rename to include/libimp/uninitialized.h index 851c657..335d001 100644 --- a/include/libimp/construct.h +++ b/include/libimp/uninitialized.h @@ -1,7 +1,7 @@ /** - * \file libimp/construct.h + * \file libimp/uninitialized.h * \author mutouyun (orz@orzz.org) - * \brief Construct an object from a memory buffer + * \brief Uninitialized memory algorithms. * \date 2022-02-27 */ #pragma once @@ -14,6 +14,7 @@ #include "libimp/def.h" #include "libimp/detect_plat.h" +#include "libimp/horrible_cast.h" LIBIMP_NAMESPACE_BEG_ @@ -67,4 +68,42 @@ void *destroy(T (*p)[N]) noexcept { return p; } +/** + * \brief Destroys a range of objects. + * \see https://en.cppreference.com/w/cpp/memory/destroy +*/ +template +void destroy(ForwardIt first, ForwardIt last) noexcept { +#if defined(LIBIMP_CPP_17) + std::destroy(first, last); +#else + for (; first != last; ++first) { + destroy(std::addressof(*first)); + } +#endif +} + +/** + * \brief Constructs objects by default-initialization + * in an uninitialized area of memory, defined by a start and a count. + * \see https://en.cppreference.com/w/cpp/memory/uninitialized_default_construct_n +*/ +template +ForwardIt uninitialized_default_construct_n(ForwardIt first, Size n) { +#if defined(LIBIMP_CPP_17) + return std::uninitialized_default_construct_n(first, n); +#else + using T = typename std::iterator_traits::value_type; + ForwardIt current = first; + LIBIMP_TRY { + for (; n > 0; (void) ++current, --n) + ::new (horrible_cast(std::addressof(*current))) T; + return current; + } LIBIMP_CATCH(...) { + destroy(first, current); + LIBIMP_THROW(, first); + } +#endif +} + LIBIMP_NAMESPACE_END_ diff --git a/include/libpmr/allocator.h b/include/libpmr/allocator.h index 9acaeae..f12eca2 100644 --- a/include/libpmr/allocator.h +++ b/include/libpmr/allocator.h @@ -10,7 +10,7 @@ #include #include "libimp/export.h" -#include "libimp/construct.h" +#include "libimp/uninitialized.h" #include "libimp/byte.h" #include "libpmr/def.h" diff --git a/include/libpmr/small_storage.h b/include/libpmr/small_storage.h index eb830ec..79c613c 100644 --- a/include/libpmr/small_storage.h +++ b/include/libpmr/small_storage.h @@ -14,28 +14,33 @@ #include #include "libimp/export.h" -#include "libimp/construct.h" +#include "libimp/uninitialized.h" #include "libimp/byte.h" #include "libimp/generic.h" #include "libpmr/def.h" +#include "libpmr/allocator.h" LIBPMR_NAMESPACE_BEG_ -class LIBIMP_EXPORT allocator; - /** * \class holder_base * \brief Data holder base class. */ class holder_base { + + // non-copyable + holder_base(holder_base const &) = delete; + holder_base &operator=(holder_base const &) = delete; + public: + holder_base() noexcept = default; virtual ~holder_base() noexcept = default; virtual bool valid() const noexcept = 0; virtual std::type_info const &type() const noexcept = 0; virtual std::size_t sizeof_type() const noexcept = 0; + virtual std::size_t sizeof_heap() const noexcept = 0; virtual std::size_t count() const noexcept = 0; - virtual std::size_t size () const noexcept = 0; virtual void *get() noexcept = 0; virtual void const *get() const noexcept = 0; @@ -55,8 +60,8 @@ public: bool valid() const noexcept override { return false; } std::type_info const &type() const noexcept override { return typeid(nullptr);} std::size_t sizeof_type() const noexcept override { return 0; } + std::size_t sizeof_heap() const noexcept override { return 0; } std::size_t count() const noexcept override { return 0; } - std::size_t size () const noexcept override { return 0; } void *get() noexcept override { return nullptr; } void const *get() const noexcept override { return nullptr; } void move_to(allocator const &, void *) noexcept override {} @@ -81,7 +86,7 @@ public: holder() = default; // uninitialized template - holder(::LIBIMP::in_place_t, A &&...args) { + holder(allocator const &, A &&...args) { ::LIBIMP::construct(value_storage_.data(), std::forward(args)...); } @@ -97,12 +102,12 @@ public: return sizeof(Value); } - std::size_t count() const noexcept override { - return 1; + std::size_t sizeof_heap() const noexcept override { + return 0; } - std::size_t size() const noexcept override { - return value_storage_.size(); + std::size_t count() const noexcept override { + return 1; } void *get() noexcept override { @@ -113,16 +118,16 @@ public: return value_storage_.data(); } - void move_to(allocator const &, void *p) noexcept override { - ::LIBIMP::construct(p, ::LIBIMP::in_place, std::move(*static_cast(get()))); + void move_to(allocator const &alloc, void *p) noexcept override { + ::LIBIMP::construct(p, alloc, std::move(*static_cast(get()))); } - void copy_to(allocator const &, void *p) const noexcept(false) override { - ::LIBIMP::construct(p, ::LIBIMP::in_place, *static_cast(get())); + void copy_to(allocator const &alloc, void *p) const noexcept(false) override { + ::LIBIMP::construct(p, alloc, *static_cast(get())); } void destroy(allocator const &) noexcept override { - ::LIBIMP::destroy(value_storage_.data()); + ::LIBIMP::destroy(static_cast(get())); } }; @@ -134,10 +139,177 @@ public: template class holder : public holder_base { - Value *value_ptr_ = nullptr; + Value *value_ptr_; public: - holder() = default; // uninitialized + holder() noexcept : value_ptr_(nullptr) {} + + template + holder(allocator const &alloc, A &&...args) { + void *p = alloc.allocate(sizeof(Value), alignof(Value)); + if (p == nullptr) return; + value_ptr_ = ::LIBIMP::construct(p, std::forward(args)...); + } + + bool valid() const noexcept override { + return value_ptr_ != nullptr; + } + + std::type_info const &type() const noexcept override { + return typeid(Value); + } + + std::size_t sizeof_type() const noexcept override { + return sizeof(Value); + } + + std::size_t sizeof_heap() const noexcept override { + return sizeof(Value); + } + + std::size_t count() const noexcept override { + return 1; + } + + void *get() noexcept override { + return value_ptr_; + } + + void const *get() const noexcept override { + return value_ptr_; + } + + void move_to(allocator const &alloc, void *p) noexcept override { + if (value_ptr_ == nullptr) { + ::LIBIMP::construct(p); + return; + } + ::LIBIMP::construct(p, alloc, std::move(*value_ptr_)); + } + + void copy_to(allocator const &alloc, void *p) const noexcept(false) override { + if (value_ptr_ == nullptr) { + ::LIBIMP::construct(p); + return; + } + ::LIBIMP::construct(p, alloc, *value_ptr_); + } + + void destroy(allocator const &alloc) noexcept override { + alloc.deallocate(::LIBIMP::destroy(value_ptr_), sizeof(Value), alignof(Value)); + } +}; + +namespace detail { + +struct holder_info { + std::type_info const *type; + std::size_t sizeof_type; + std::size_t count; + void (*copy)(allocator const &, void const *s, void *d); +}; + +template +void *holder_construct(allocator const &alloc, std::size_t n, Construct &&c) { + void *p = alloc.allocate(n * sizeof(Value)); + if (p == nullptr) return nullptr; + LIBIMP_TRY { + std::forward(c)(static_cast(p), n); + return p; + } LIBIMP_CATCH(...) { + alloc.deallocate(p, n * sizeof(Value)); + LIBIMP_THROW(, nullptr); + } +} + +} // namespace detail + +/** + * \class template <> holder + * \brief A holder implementation of some data objects that stores type information on stack memory. + */ +template <> +class holder : public holder_base { + + detail::holder_info info_; + void *value_ptr_; + +public: + holder() noexcept + : info_{&typeid(nullptr)} + , value_ptr_(nullptr) {} + + /// \brief Constructs a holder with type of `Value`. + /// alignof(Value) must be less than or equal to alignof(std::max_align_t). + template = true> + holder(allocator const &alloc, ::LIBIMP::types, std::size_t n) : holder() { + value_ptr_ = detail::holder_construct(alloc, n, + ::LIBIMP::uninitialized_default_construct_n); + if (value_ptr_ == nullptr) return; + info_.type = &typeid(Value); + info_.sizeof_type = sizeof(Value); + info_.count = n; + info_.copy = [](allocator const &alloc, void const *s, void *d) { + auto const &src = *static_cast(s); + auto & dst = *::LIBIMP::construct(d); + if (!src.valid()) return; + dst.value_ptr_ = detail::holder_construct(alloc, src.count(), [s = src.get()](Value *d, std::size_t n) { + std::uninitialized_copy_n(static_cast(s), n, d); + }); + if (dst.value_ptr_ == nullptr) return; + dst.info_ = src.info_; + }; + } + + bool valid() const noexcept override { + return value_ptr_ != nullptr; + } + + std::type_info const &type() const noexcept override { + return *info_.type; + } + + std::size_t sizeof_type() const noexcept override { + return info_.sizeof_type; + } + + std::size_t sizeof_heap() const noexcept override { + return info_.sizeof_type * info_.count; + } + + std::size_t count() const noexcept override { + return info_.count; + } + + void *get() noexcept override { + return value_ptr_; + } + + void const *get() const noexcept override { + return value_ptr_; + } + + void move_to(allocator const &, void *p) noexcept override { + auto *des = ::LIBIMP::construct(p); + if (value_ptr_ == nullptr) { + return; + } + std::swap(value_ptr_, des->value_ptr_); + std::swap(info_, des->info_); + } + + void copy_to(allocator const &alloc, void *p) const noexcept(false) override { + auto *des = ::LIBIMP::construct(p); + if (value_ptr_ == nullptr) { + return; + } + info_.copy(alloc, this, des); + } + + void destroy(allocator const &alloc) noexcept override { + alloc.deallocate(::LIBIMP::destroy(value_ptr_), info_.sizeof_type * info_.count); + } }; LIBPMR_NAMESPACE_END_ diff --git a/test/imp/test_imp_utility.cpp b/test/imp/test_imp_utility.cpp index 1de8bb4..3c6ba5d 100644 --- a/test/imp/test_imp_utility.cpp +++ b/test/imp/test_imp_utility.cpp @@ -5,7 +5,7 @@ #include "gtest/gtest.h" -#include "libimp/construct.h" +#include "libimp/uninitialized.h" #include "libimp/pimpl.h" #include "libimp/countof.h" #include "libimp/dataof.h" diff --git a/test/pmr/test_pmr_small_storage.cpp b/test/pmr/test_pmr_small_storage.cpp index e3629a3..6b5e1d9 100644 --- a/test/pmr/test_pmr_small_storage.cpp +++ b/test/pmr/test_pmr_small_storage.cpp @@ -1,7 +1,86 @@ +#include +#include + #include "gtest/gtest.h" #include "libpmr/small_storage.h" +TEST(small_storage, holder_construct) { + pmr::holder_null(); + pmr::holder(); + pmr::holder(); + pmr::holder(); + SUCCEED(); +} + +TEST(small_storage, holder_copy_move_construct) { + EXPECT_FALSE(std::is_copy_constructible::value); + EXPECT_FALSE((std::is_copy_constructible>::value)); + EXPECT_FALSE((std::is_copy_constructible>::value)); + EXPECT_FALSE((std::is_copy_constructible>::value)); + + EXPECT_FALSE(std::is_copy_assignable::value); + EXPECT_FALSE((std::is_copy_assignable>::value)); + EXPECT_FALSE((std::is_copy_assignable>::value)); + EXPECT_FALSE((std::is_copy_assignable>::value)); + + EXPECT_FALSE(std::is_move_constructible::value); + EXPECT_FALSE((std::is_move_constructible>::value)); + EXPECT_FALSE((std::is_move_constructible>::value)); + EXPECT_FALSE((std::is_move_constructible>::value)); + + EXPECT_FALSE(std::is_move_assignable::value); + EXPECT_FALSE((std::is_move_assignable>::value)); + EXPECT_FALSE((std::is_move_assignable>::value)); + EXPECT_FALSE((std::is_move_assignable>::value)); +} + +TEST(small_storage, holder_copy_move) { + struct foo { + int i; + foo(int i) : i(i) {} + foo(foo const &rhs) : i(rhs.i) {} + foo(foo &&rhs) : i(std::exchange(rhs.i, 0)) {} + }; + + pmr::allocator alc; + pmr::holder h1(alc, 1); + pmr::holder h2, h3; // uninitialized + h1.copy_to(alc, &h2); + EXPECT_EQ(static_cast(h1.get())->i, 1); + EXPECT_EQ(static_cast(h2.get())->i, 1); + h1.move_to(alc, &h3); + EXPECT_EQ(static_cast(h1.get())->i, 0); + EXPECT_EQ(static_cast(h3.get())->i, 1); + h1.destroy(alc); + h2.destroy(alc); + h3.destroy(alc); + + pmr::holder h4(alc, 1); + pmr::holder h5, h6; // uninitialized + h4.copy_to(alc, &h5); + EXPECT_EQ(static_cast(h4.get())->i, 1); + EXPECT_EQ(static_cast(h5.get())->i, 1); + h4.move_to(alc, &h6); + EXPECT_EQ(static_cast(h4.get())->i, 0); + EXPECT_EQ(static_cast(h6.get())->i, 1); + h4.destroy(alc); + h5.destroy(alc); + h6.destroy(alc); + + pmr::holder h7(alc, ::LIBIMP::types{}, 10); + pmr::holder h8, h9; // uninitialized + h7.copy_to(alc, &h8); + EXPECT_EQ(h7.count(), 10); + EXPECT_EQ(h8.count(), 10); + h7.move_to(alc, &h9); + EXPECT_EQ(h7.count(), 0); + EXPECT_EQ(h9.count(), 10); + h7.destroy(alc); + h8.destroy(alc); + h9.destroy(alc); +} + TEST(small_storage, construct) { }