From 43b7fa2b6f15ac82c91659237c8b76e2c353921a Mon Sep 17 00:00:00 2001
From: KRM7 <70973547+KRM7@users.noreply.github.com>
Date: Tue, 20 Feb 2024 10:03:01 +0100
Subject: [PATCH] add size parameter (closes #6)
---
README.md | 17 +--
src/small_unique_ptr.hpp | 263 ++++++++++++++++++--------------------
test/small_unique_ptr.cpp | 31 ++++-
3 files changed, 157 insertions(+), 154 deletions(-)
diff --git a/README.md b/README.md
index 51b682f..1104f92 100644
--- a/README.md
+++ b/README.md
@@ -10,25 +10,26 @@ 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 the size of the internal stack buffer
- - Their required alignment is not greater than 64
+ - Their alignment is not greater than the alignment of the stack buffer
- Their move constructor is `noexcept`
-The size of the stack buffer is architecture dependent, but on 64 bit architectures it will
-generally be:
+The size of the stack buffer depends on the architecture and the overall size of the
+`small_unique_ptr` object, but the default values on 64 bit architectures will typically 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
- - 56 for array types
+ - 56 for array types (rounded down to a multiple of the element size)
-The overall size of a `small_unique_ptr` object is:
+The overall size of a `small_unique_ptr` object for a polymorphic type is:
- - 64 if `T` may be allocated in the stack buffer
+ - `Size` if `T` may be allocated in the stack buffer (64 by default)
- `sizeof(T*)` otherwise
-The interface matches `std::unique_ptr`, except for:
+The interface matches `std::unique_ptr`, except for:
- There is no `Deleter` template parameter or any of the associated methods
+ - There is a `Size` template parameter that specifies the (maximum) size of the `small_unique_ptr` object
- Constructors from pointers are not provided except for the nullptr constructor
- `release()` is not implemented
- `T` can't be an incomplete type
@@ -36,7 +37,7 @@ The interface matches `std::unique_ptr`, except for:
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.
+`std::unique_ptr` would be.
--------------------------------------------------------------------------------------------------
diff --git a/src/small_unique_ptr.hpp b/src/small_unique_ptr.hpp
index db6619f..e163c23 100644
--- a/src/small_unique_ptr.hpp
+++ b/src/small_unique_ptr.hpp
@@ -7,6 +7,8 @@
#include
#include
#include
+#include
+#include
#include
#include
#include
@@ -23,6 +25,8 @@ namespace detail
constexpr const T& max(const T& left, const T& right) { return (left < right) ? right : left; }
+ using move_fn = void(*)(void* src, void* dst) noexcept;
+
template
void move_buffer(void* src, void* dst) noexcept(std::is_nothrow_move_constructible_v)
{
@@ -55,80 +59,62 @@ namespace detail
inline constexpr bool is_proper_base_of_v = is_proper_base_of::value;
- inline constexpr std::size_t small_ptr_size = 64;
+ inline constexpr std::size_t default_small_ptr_size = 64;
- template
+ template
struct buffer_size
{
private:
- static constexpr std::size_t dynamic_buffer_size = small_ptr_size - sizeof(T*) - !has_virtual_move * sizeof(decltype(&move_buffer));
+ static constexpr std::size_t dynamic_buffer_size = small_ptr_size - sizeof(T*) - !has_virtual_move * sizeof(move_fn);
static constexpr std::size_t static_buffer_size = detail::min(sizeof(T), small_ptr_size - sizeof(T*));
public:
static constexpr std::size_t value = std::has_virtual_destructor_v ? dynamic_buffer_size : static_buffer_size;
};
- template
- struct buffer_size
+ template
+ struct buffer_size
{
static constexpr std::size_t value = small_ptr_size - sizeof(T*);
};
- template
- inline constexpr std::size_t buffer_size_v = buffer_size::value;
+ template
+ inline constexpr std::size_t buffer_size_v = buffer_size::value;
- template
+ template
struct buffer_alignment
{
private:
- static constexpr std::size_t dynamic_buffer_alignment = small_ptr_size;
- static constexpr std::size_t static_buffer_alignment = detail::min(alignof(T), small_ptr_size);
+ static constexpr std::size_t dynamic_buffer_alignment = std::gcd(std::bit_floor(small_ptr_size), small_ptr_size);
+ static constexpr std::size_t static_buffer_alignment = std::gcd(std::bit_floor(min(alignof(T), small_ptr_size)), min(alignof(T), small_ptr_size));
public:
static constexpr std::size_t value = std::has_virtual_destructor_v ? dynamic_buffer_alignment : static_buffer_alignment;
};
- template
- inline constexpr std::size_t buffer_alignment_v = buffer_alignment::value;
-
+ template
+ inline constexpr std::size_t buffer_alignment_v = buffer_alignment::value;
- template
- struct buffer_elements {};
- template
- struct buffer_elements
- {
- static constexpr std::size_t value = buffer_size_v / sizeof(T);
- };
-
- template
- inline constexpr std::size_t buffer_elements_v = buffer_elements::value;
-
-
- template
+ template
struct is_always_heap_allocated
{
- static constexpr bool value = (sizeof(T) > buffer_size_v) || (alignof(T) > buffer_alignment_v) ||
- (!std::is_abstract_v && !std::is_nothrow_move_constructible_v>);
- };
+ using U = std::remove_cv_t>;
- template
- struct is_always_heap_allocated
- {
- static constexpr bool value = (sizeof(T) > buffer_size_v) || (alignof(T) > buffer_alignment_v) ||
- !std::is_nothrow_move_constructible_v>;
+ static constexpr bool value = (sizeof(U) > buffer_size_v) ||
+ (alignof(U) > buffer_alignment_v) ||
+ (!std::is_abstract_v && !std::is_nothrow_move_constructible_v);
};
- template
- inline constexpr bool is_always_heap_allocated_v = is_always_heap_allocated::value;
+ template
+ inline constexpr bool is_always_heap_allocated_v = is_always_heap_allocated::value;
- template
+ template
struct small_unique_ptr_base
{
using pointer = std::remove_cv_t*;
- using buffer_t = unsigned char[buffer_size_v];
- using move_fn = void(*)(void*, void*) noexcept;
+ using buffer_t = unsigned char[buffer_size_v];
pointer buffer(std::ptrdiff_t offset = 0) const noexcept
{
@@ -136,7 +122,7 @@ namespace detail
}
template
- void move_buffer_to(small_unique_ptr_base& dst) noexcept
+ void move_buffer_to(small_unique_ptr_base& dst) noexcept
{
move_(std::launder(buffer()), dst.buffer());
dst.move_ = move_;
@@ -147,23 +133,23 @@ namespace detail
return static_cast(move_);
}
- alignas(buffer_alignment_v) mutable buffer_t buffer_ = {};
+ alignas(buffer_alignment_v) mutable buffer_t buffer_ = {};
T* data_ = nullptr;
move_fn move_ = nullptr;
};
- template
- requires(is_always_heap_allocated_v)
- struct small_unique_ptr_base
+ template
+ requires(is_always_heap_allocated_v)
+ struct small_unique_ptr_base
{
static constexpr bool is_stack_allocated() noexcept { return false; }
std::remove_extent_t* data_ = nullptr;
};
- template
- requires(!is_always_heap_allocated_v && !std::is_polymorphic_v && !std::is_array_v)
- struct small_unique_ptr_base
+ template
+ requires(!is_always_heap_allocated_v && !std::is_polymorphic_v && !std::is_array_v)
+ struct small_unique_ptr_base
{
using pointer = std::remove_cv_t*;
using buffer_t = std::remove_cv_t;
@@ -174,7 +160,7 @@ namespace detail
}
template
- constexpr void move_buffer_to(small_unique_ptr_base& dst) noexcept
+ constexpr void move_buffer_to(small_unique_ptr_base& dst) noexcept
{
std::construct_at(dst.buffer(), std::move(*buffer()));
}
@@ -191,12 +177,12 @@ namespace detail
T* data_ = nullptr;
};
- template
- requires(!is_always_heap_allocated_v && has_virtual_move)
- struct small_unique_ptr_base
+ template
+ requires(!is_always_heap_allocated_v && has_virtual_move)
+ struct small_unique_ptr_base
{
using pointer = std::remove_cv_t*;
- using buffer_t = unsigned char[buffer_size_v];
+ using buffer_t = unsigned char[buffer_size_v];
pointer buffer(std::ptrdiff_t offset = 0) const noexcept
{
@@ -205,7 +191,7 @@ namespace detail
template
requires(has_virtual_move)
- void move_buffer_to(small_unique_ptr_base& dst) noexcept
+ void move_buffer_to(small_unique_ptr_base& dst) noexcept
{
const pointer data = const_cast(data_);
data->small_unique_ptr_move(dst.buffer());
@@ -217,23 +203,25 @@ namespace detail
auto* data = reinterpret_cast(data_);
auto* buffer_first = static_cast(buffer_);
- auto* buffer_last = buffer_first + buffer_size_v;
+ auto* buffer_last = buffer_first + buffer_size_v;
- assert(reinterpret_cast(buffer_last) - reinterpret_cast(buffer_first) == buffer_size_v);
+ assert(reinterpret_cast(buffer_last) - reinterpret_cast(buffer_first) == (buffer_size_v));
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;
};
- template
- requires(!is_always_heap_allocated_v && std::is_array_v)
- struct small_unique_ptr_base
+ template
+ requires(!is_always_heap_allocated_v && std::is_array_v)
+ struct small_unique_ptr_base
{
+ static constexpr std::size_t array_size = buffer_size_v / sizeof(std::remove_extent_t);
+
using pointer = std::remove_cv_t>*;
- using buffer_t = std::remove_cv_t>[buffer_elements_v];
+ using buffer_t = std::remove_cv_t>[array_size];
constexpr pointer buffer(std::ptrdiff_t = 0) const noexcept
{
@@ -241,9 +229,9 @@ namespace detail
}
template
- void move_buffer_to(small_unique_ptr_base& dst) noexcept
+ void move_buffer_to(small_unique_ptr_base& dst) noexcept
{
- std::uninitialized_move(buffer(), buffer() + buffer_elements_v, dst.buffer());
+ std::uninitialized_move(buffer(), buffer() + array_size, dst.buffer());
}
constexpr bool is_stack_allocated() const noexcept
@@ -263,11 +251,13 @@ namespace detail
} // namespace detail
-template
-class small_unique_ptr : private detail::small_unique_ptr_base
+template
+class small_unique_ptr : private detail::small_unique_ptr_base
{
public:
- static_assert(!std::is_bounded_array_v);
+ static_assert(!std::is_bounded_array_v, "Only unbounded array types are supported.");
+ static_assert(Size >= 2 * sizeof(T*), "The size must be at least the size of 2 pointers.");
+ static_assert(Size % sizeof(T*) == 0, "The size must be a multiple of the size of a pointer.");
using element_type = std::remove_extent_t;
using pointer = std::remove_extent_t*;
@@ -283,7 +273,7 @@ class small_unique_ptr : private detail::small_unique_ptr_base
{}
template
- constexpr small_unique_ptr(small_unique_ptr&& other, constructor_tag_t = {}) noexcept
+ constexpr small_unique_ptr(small_unique_ptr&& other, constructor_tag_t = {}) noexcept
{
static_assert(!detail::is_proper_base_of_v || std::has_virtual_destructor_v);
@@ -292,7 +282,7 @@ class small_unique_ptr : private detail::small_unique_ptr_base
this->data_ = std::exchange(other.data_, nullptr);
return;
}
- if constexpr (!detail::is_always_heap_allocated_v) // other.is_stack_allocated()
+ if constexpr (!detail::is_always_heap_allocated_v) // other.is_stack_allocated()
{
other.move_buffer_to(*this);
this->data_ = std::launder(this->buffer(other.template offsetof_base()));
@@ -308,7 +298,7 @@ class small_unique_ptr : private detail::small_unique_ptr_base
}
template
- constexpr small_unique_ptr& operator=(small_unique_ptr&& other) noexcept
+ constexpr small_unique_ptr& operator=(small_unique_ptr&& other) noexcept
{
static_assert(!detail::is_proper_base_of_v || std::has_virtual_destructor_v);
@@ -317,7 +307,7 @@ class small_unique_ptr : private detail::small_unique_ptr_base
reset(std::exchange(other.data_, nullptr));
return *this;
}
- if constexpr (!detail::is_always_heap_allocated_v) // other.is_stack_allocated()
+ if constexpr (!detail::is_always_heap_allocated_v) // other.is_stack_allocated()
{
reset();
other.move_buffer_to(*this);
@@ -347,7 +337,7 @@ class small_unique_ptr : private detail::small_unique_ptr_base
constexpr void swap(small_unique_ptr& other) noexcept
{
- if constexpr (detail::is_always_heap_allocated_v)
+ if constexpr (small_unique_ptr::is_always_heap_allocated())
{
std::swap(this->data_, other.data_);
}
@@ -357,22 +347,9 @@ class small_unique_ptr : private detail::small_unique_ptr_base
}
else if (is_stack_allocated() && other.is_stack_allocated())
{
- const std::ptrdiff_t other_offset = other.offsetof_base();
- const std::ptrdiff_t this_offset = this->offsetof_base();
-
- detail::small_unique_ptr_base temp;
-
- other.move_buffer_to(temp);
- temp.data_ = std::launder(temp.buffer(other_offset));
- std::destroy_at(other.data_);
-
- this->move_buffer_to(other);
- other.data_ = std::launder(other.buffer(this_offset));
- std::destroy_at(this->data_);
-
- temp.move_buffer_to(*this);
- this->data_ = std::launder(this->buffer(other_offset));
- std::destroy_at(temp.data_);
+ small_unique_ptr temp = std::move(other);
+ other = std::move(*this);
+ *this = std::move(temp);
}
else if (!is_stack_allocated() && other.is_stack_allocated())
{
@@ -394,7 +371,7 @@ class small_unique_ptr : private detail::small_unique_ptr_base
[[nodiscard]]
static constexpr bool is_always_heap_allocated() noexcept
{
- return detail::is_always_heap_allocated_v;
+ return detail::is_always_heap_allocated_v;
}
[[nodiscard]]
@@ -406,8 +383,14 @@ class small_unique_ptr : private detail::small_unique_ptr_base
[[nodiscard]]
static constexpr std::size_t stack_buffer_size() noexcept
{
- if constexpr (detail::is_always_heap_allocated_v) return 0;
- else return detail::buffer_size_v;
+ if constexpr (is_always_heap_allocated()) return 0;
+ else return sizeof(typename small_unique_ptr::buffer_t);
+ }
+
+ [[nodiscard]]
+ static constexpr std::size_t stack_array_size() noexcept requires(std::is_array_v)
+ {
+ return stack_buffer_size() / sizeof(std::remove_extent_t);
}
[[nodiscard]]
@@ -460,7 +443,7 @@ class small_unique_ptr : private detail::small_unique_ptr_base
}
template
- constexpr std::strong_ordering operator<=>(const small_unique_ptr& rhs) const noexcept
+ constexpr std::strong_ordering operator<=>(const small_unique_ptr& rhs) const noexcept
{
return this->data_ <=> rhs.data_;
}
@@ -501,10 +484,10 @@ class small_unique_ptr : private detail::small_unique_ptr_base
constexpr void destroy() noexcept requires(std::is_array_v)
{
- is_stack_allocated() ? std::destroy(this->data_, this->data_ + detail::buffer_elements_v) : delete[] this->data_;
+ is_stack_allocated() ? std::destroy(this->data_, this->data_ + stack_array_size()) : delete[] this->data_;
}
- template
+ template
friend class small_unique_ptr;
friend struct detail::make_unique_small_impl;
@@ -512,12 +495,12 @@ class small_unique_ptr : private detail::small_unique_ptr_base
namespace std
{
- template
- struct hash>
+ template
+ struct hash>
{
- std::size_t operator()(const small_unique_ptr& p) const noexcept
+ std::size_t operator()(const small_unique_ptr& p) const noexcept
{
- return std::hash::pointer>{}(p.get());
+ return std::hash::pointer>{}(p.get());
}
};
@@ -527,22 +510,22 @@ namespace detail
{
struct make_unique_small_impl
{
- template
- static constexpr small_unique_ptr invoke_scalar(Args&&... args)
- noexcept(std::is_nothrow_constructible_v && !detail::is_always_heap_allocated_v)
+ template
+ static constexpr small_unique_ptr invoke_scalar(Args&&... args)
+ noexcept(std::is_nothrow_constructible_v && !detail::is_always_heap_allocated_v)
{
- small_unique_ptr ptr;
+ small_unique_ptr ptr;
- if (detail::is_always_heap_allocated_v || std::is_constant_evaluated())
+ if (detail::is_always_heap_allocated_v || std::is_constant_evaluated())
{
ptr.data_ = new T(std::forward(args)...);
}
- else if constexpr (!detail::is_always_heap_allocated_v && std::is_polymorphic_v && !detail::has_virtual_move)
+ else if constexpr (!detail::is_always_heap_allocated_v && std::is_polymorphic_v && !detail::has_virtual_move)
{
ptr.data_ = std::construct_at(ptr.buffer(), std::forward(args)...);
ptr.move_ = detail::move_buffer>;
}
- else if constexpr (!detail::is_always_heap_allocated_v)
+ else if constexpr (!detail::is_always_heap_allocated_v)
{
ptr.data_ = std::construct_at(ptr.buffer(), std::forward(args)...);
}
@@ -550,40 +533,40 @@ namespace detail
return ptr;
}
- template
- static constexpr small_unique_ptr invoke_array(std::size_t count)
+ template
+ static constexpr small_unique_ptr invoke_array(std::size_t count)
{
- small_unique_ptr ptr;
+ small_unique_ptr ptr;
- if (detail::is_always_heap_allocated_v || (count > detail::buffer_elements_v) || std::is_constant_evaluated())
+ if (detail::is_always_heap_allocated_v || (ptr.stack_array_size() < count) || std::is_constant_evaluated())
{
ptr.data_ = new std::remove_extent_t[count]{};
}
- else if constexpr (!detail::is_always_heap_allocated_v)
+ else if constexpr (!detail::is_always_heap_allocated_v)
{
- std::uninitialized_value_construct_n(ptr.buffer(), detail::buffer_elements_v);
+ std::uninitialized_value_construct_n(ptr.buffer(), ptr.stack_array_size());
ptr.data_ = std::launder(ptr.buffer());
}
return ptr;
}
- template
- static constexpr small_unique_ptr invoke_for_overwrite_scalar()
- noexcept(std::is_nothrow_default_constructible_v && !detail::is_always_heap_allocated_v)
+ template
+ static constexpr small_unique_ptr invoke_for_overwrite_scalar()
+ noexcept(std::is_nothrow_default_constructible_v && !detail::is_always_heap_allocated_v)
{
- small_unique_ptr ptr;
+ small_unique_ptr ptr;
- if (detail::is_always_heap_allocated_v || std::is_constant_evaluated())
+ if (detail::is_always_heap_allocated_v || std::is_constant_evaluated())
{
ptr.data_ = new T;
}
- else if constexpr (!detail::is_always_heap_allocated_v && std::is_polymorphic_v && !detail::has_virtual_move)
+ else if constexpr (!detail::is_always_heap_allocated_v && std::is_polymorphic_v && !detail::has_virtual_move