From 88191f922d411766245d0515a73abba3da4cf640 Mon Sep 17 00:00:00 2001 From: KRM7 <70973547+KRM7@users.noreply.github.com> Date: Fri, 16 Feb 2024 23:02:49 +0100 Subject: [PATCH] update README --- .github/workflows/sanitizers.yml | 2 +- README.md | 107 +++++++++++++++++++++++++++++-- core-guidelines.ruleset | 2 +- src/small_unique_ptr.hpp | 27 ++++---- 4 files changed, 117 insertions(+), 21 deletions(-) diff --git a/.github/workflows/sanitizers.yml b/.github/workflows/sanitizers.yml index 0bc70f1..b36145c 100644 --- a/.github/workflows/sanitizers.yml +++ b/.github/workflows/sanitizers.yml @@ -14,7 +14,7 @@ jobs: pkgs: clang-15 llvm-15 env: - ASAN_OPTIONS: check_initialization_order=1:strict_init_order=1:detect_stack_use_after_return=1:detect_leaks=1 + ASAN_OPTIONS: check_initialization_order=1:strict_init_order=1:detect_stack_use_after_return=1:detect_leaks=1:detect_invalid_pointer_pairs=2 UBSAN_OPTIONS: print_stacktrace=1:print_summary=1 defaults: diff --git a/README.md b/README.md index ae155ba..ccf3e6a 100644 --- a/README.md +++ b/README.md @@ -1,14 +1,27 @@ -A fully constexpr unique_ptr implementation in C++20 with small object optimization. + +### `small_unique_ptr` + +A constexpr `unique_ptr` implementation in C++20 with small object optimization. + +```cpp +small_unique_ptr p = make_unique_small(); +``` Objects created with `make_unique_small` are allocated on the stack if: - - Their size is not greater than `64 - 2 * sizeof(void*)` + - Their size is not greater than the size of the internal stack buffer - Their required alignment is not greater than `64` - - Their move constructor is declared as `noexcept` + - Their move constructor is `noexcept` + +The size of the stack buffer is architecture dependent, but it will generally be: + + - `48` for polymorphic types + - `56` for polymorphic types that implement a virtual `small_unique_ptr_move` method + - `sizeof(T)` for non-polymophic types, with an upper limit of `56` -The size of a `small_unique_ptr` object is: +The overall size of a `small_unique_ptr` object is: - - `64` if T can be allocated in the stack buffer + - `64` if `T` may be allocated in the stack buffer - `sizeof(T*)` otherwise The interface matches `std::unique_ptr`, except for: @@ -19,5 +32,85 @@ The interface matches `std::unique_ptr`, except for: - `T` can't be an incomplete type or an array type - There are a couple of extra methods for checking where objects are allocated -The stack buffer is not used in constant evaluated contexts, so any constexpr usage -is subject to the same transient allocation requirements that `std::unique_ptr` would be. +Everything is constexpr, but the stack buffer is not used in constant evaluated contexts, +so any constexpr usage is subject to the same transient allocation requirements that a constexpr +`std::unique_ptr` would be. + +
+ +Example of a simplified std::move_only_function implementation using +small_unique_ptr + + +```cpp +template +class move_only_function; + +template +class move_only_function +{ +public: + constexpr move_only_function() noexcept = default; + constexpr move_only_function(std::nullptr_t) noexcept {} + + template + requires(!std::is_same_v && std::is_invocable_r_v) + constexpr move_only_function(F f) noexcept(noexcept(make_unique_small>(std::move(f)))) : + fptr_(make_unique_small>(std::move(f))) + {} + + template + requires(!std::is_same_v && std::is_invocable_r_v) + constexpr move_only_function& operator=(F f) noexcept(noexcept(make_unique_small>(std::move(f)))) + { + fptr_ = make_unique_small>(std::move(f)); + return *this; + } + + constexpr move_only_function(move_only_function&&) = default; + constexpr move_only_function& operator=(move_only_function&&) = default; + + constexpr Ret operator()(Args... args) const + { + return fptr_->invoke(std::forward(args)...); + } + + constexpr void swap(move_only_function& other) noexcept + { + fptr_.swap(other.fptr_); + } + + constexpr explicit operator bool() const noexcept { return static_cast(fptr_); } + +private: + struct ImplBase + { + constexpr virtual Ret invoke(Args...) = 0; + constexpr virtual void small_unique_ptr_move(void* dst) noexcept = 0; + constexpr virtual ~ImplBase() = default; + }; + + template + struct Impl : public ImplBase + { + constexpr Impl(Callable func) noexcept(std::is_nothrow_move_constructible_v) : + func_(std::move(func)) + {} + + constexpr void small_unique_ptr_move(void* dst) noexcept override + { + std::construct_at(static_cast(dst), std::move(*this)); + } + + constexpr Ret invoke(Args... args) override + { + return std::invoke(func_, std::forward(args)...); + } + + Callable func_; + }; + + small_unique_ptr fptr_ = nullptr; +}; +``` +
diff --git a/core-guidelines.ruleset b/core-guidelines.ruleset index ebf549b..f379195 100644 --- a/core-guidelines.ruleset +++ b/core-guidelines.ruleset @@ -1,5 +1,5 @@  - + diff --git a/src/small_unique_ptr.hpp b/src/small_unique_ptr.hpp index f31cb09..c761ce3 100644 --- a/src/small_unique_ptr.hpp +++ b/src/small_unique_ptr.hpp @@ -116,8 +116,7 @@ namespace detail pointer buffer(std::ptrdiff_t offset = 0) const noexcept { - assert(0 <= offset && offset < buffer_size_v); - return std::launder(reinterpret_cast(std::addressof(buffer_[offset]))); + return std::launder(reinterpret_cast(static_cast(buffer_) + offset)); } template @@ -127,9 +126,12 @@ namespace detail dst.move_ = move_; } - constexpr bool is_stack_allocated() const noexcept { return static_cast(this->move_); } + constexpr bool is_stack_allocated() const noexcept + { + return static_cast(this->move_); + } - alignas(buffer_alignment_v) mutable buffer_t buffer_ = {}; + alignas(buffer_alignment_v) mutable buffer_t buffer_; T* data_ = nullptr; move_fn move_ = nullptr; }; @@ -152,8 +154,7 @@ namespace detail pointer buffer(std::ptrdiff_t offset = 0) const noexcept { - assert(0 <= offset && offset < buffer_size_v); - return std::launder(reinterpret_cast(std::addressof(buffer_[offset]))); + return std::launder(reinterpret_cast(static_cast(buffer_) + offset)); } template @@ -162,9 +163,12 @@ namespace detail std::construct_at(dst.buffer(), std::move(*this->buffer())); } - constexpr bool is_stack_allocated() const noexcept { return !std::is_constant_evaluated() && (data_ == this->buffer()); } + constexpr bool is_stack_allocated() const noexcept + { + return !std::is_constant_evaluated() && (data_ == this->buffer()); + } - alignas(buffer_alignment_v) mutable buffer_t buffer_ = {}; + alignas(buffer_alignment_v) mutable buffer_t buffer_; T* data_ = nullptr; }; @@ -177,8 +181,7 @@ namespace detail pointer buffer(std::ptrdiff_t offset = 0) const noexcept { - assert(0 <= offset && offset < buffer_size_v); - return std::launder(reinterpret_cast(std::addressof(buffer_[offset]))); + return std::launder(reinterpret_cast(static_cast(buffer_) + offset)); } template @@ -202,7 +205,7 @@ namespace detail return std::less_equal{}(buffer_first, data) && std::less{}(data, buffer_last); } - alignas(buffer_alignment_v) mutable buffer_t buffer_ = {}; + alignas(buffer_alignment_v) mutable buffer_t buffer_; T* data_ = nullptr; }; @@ -453,7 +456,7 @@ namespace detail struct make_unique_small_impl { template - static constexpr small_unique_ptr invoke(Args... args) + static constexpr small_unique_ptr invoke(Args&&... args) noexcept(std::is_nothrow_constructible_v && !detail::is_always_heap_allocated_v) { small_unique_ptr ptr;