-
Notifications
You must be signed in to change notification settings - Fork 5
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
3 changed files
with
615 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,319 @@ | ||
/* Copyright (c) 2023 Krisztián Rugási. Subject to the MIT License. */ | ||
|
||
#ifndef GA_UTILITY_SMALL_UNIQUE_PTR_HPP | ||
#define GA_UTILITY_SMALL_UNIQUE_PTR_HPP | ||
|
||
#include "type_traits.hpp" | ||
#include "utility.hpp" | ||
#include <compare> | ||
#include <memory> | ||
#include <type_traits> | ||
#include <utility> | ||
#include <cstddef> | ||
|
||
namespace gapp::detail | ||
{ | ||
template<typename T> | ||
inline constexpr size_t buffer_size_v = 64 - sizeof(T*) - sizeof(void(*)()); | ||
|
||
// should be inside small_unique_ptr | ||
template<typename T> | ||
inline constexpr bool always_heap_allocated_v = (sizeof(T) > buffer_size_v<T>) || (!std::is_nothrow_move_constructible_v<T> && !std::is_abstract_v<T>); | ||
|
||
|
||
template<typename T, bool = always_heap_allocated_v<T>> | ||
class small_ptr_storage | ||
{ | ||
public: | ||
constexpr small_ptr_storage() noexcept {} | ||
constexpr ~small_ptr_storage() noexcept {} | ||
|
||
small_ptr_storage(const small_ptr_storage&) = delete; | ||
small_ptr_storage(small_ptr_storage&&) = delete; | ||
|
||
small_ptr_storage& operator=(const small_ptr_storage&) = delete; | ||
small_ptr_storage& operator=(small_ptr_storage&&) = delete; | ||
|
||
constexpr T* buffer() noexcept | ||
{ | ||
if constexpr (!std::is_abstract_v<T>) return std::addressof(buffer_); | ||
else return reinterpret_cast<T*>(std::addressof(buffer_)); | ||
} | ||
|
||
constexpr const T* buffer() const noexcept | ||
{ | ||
if constexpr (!std::is_abstract_v<T>) return std::addressof(buffer_); | ||
else return reinterpret_cast<const T*>(std::addressof(buffer_)); | ||
} | ||
|
||
template<typename U> | ||
constexpr void move_buffer_to(small_ptr_storage<U>& dst) noexcept | ||
{ | ||
move_(buffer(), dst.buffer()); | ||
dst.move_ = move_; | ||
} | ||
|
||
using buffer_t = std::conditional_t<std::is_abstract_v<T>, unsigned char[sizeof(T)], T>; | ||
using move_t = void(*)(void* src, void* dst) noexcept; | ||
|
||
move_t move_ = nullptr; | ||
union | ||
{ | ||
unsigned char padding_[buffer_size_v<T>]; | ||
alignas(T) buffer_t buffer_; | ||
}; | ||
}; | ||
|
||
template<typename T> | ||
class small_ptr_storage<T, true> | ||
{}; | ||
|
||
|
||
template<typename T> // TODO: move CACHE_LINE_SIZE constant from small_vector to utility and use that here too | ||
class small_unique_ptr : private small_ptr_storage<std::remove_cv_t<T>> | ||
{ | ||
public: | ||
static_assert(!std::is_array_v<T> && !std::is_void_v<T>); | ||
|
||
using element_type = T; | ||
using pointer = T*; | ||
using reference = T&; | ||
|
||
constexpr small_unique_ptr() noexcept = default; | ||
constexpr small_unique_ptr(std::nullptr_t) noexcept {} | ||
|
||
constexpr small_unique_ptr(small_unique_ptr&& other) noexcept : | ||
small_unique_ptr<T>(std::move(other), detail::empty_t{}) | ||
{} | ||
|
||
template<typename U> | ||
constexpr small_unique_ptr(small_unique_ptr<U>&& other, detail::empty_t = {}) noexcept | ||
{ | ||
static_assert(!detail::is_proper_base_of_v<T, U> || std::has_virtual_destructor_v<T>); | ||
|
||
if (!other.is_local() || std::is_constant_evaluated()) | ||
{ | ||
data_ = std::exchange(other.data_, nullptr); | ||
return; | ||
} | ||
//if constexpr (!always_heap_allocated_v<U> && !std::has_virtual_destructor_v<T>) | ||
//{ | ||
// std::construct_at(this->buffer(), std::move(*other.buffer())); | ||
// data_ = this->buffer(); | ||
// other.reset(); | ||
//} | ||
if constexpr (!always_heap_allocated_v<U>) // other.is_local() | ||
{ | ||
other.move_buffer_to(*this); | ||
data_ = reinterpret_cast<T*>(reinterpret_cast<unsigned char*>(this->buffer()) + offsetof_base(other)); | ||
other.reset(); | ||
} | ||
} | ||
|
||
constexpr small_unique_ptr& operator=(small_unique_ptr&& other) noexcept | ||
{ | ||
if (std::addressof(other) == this) [[unlikely]] return *this; | ||
|
||
return operator=<T>(std::move(other)); | ||
} | ||
|
||
template<typename U> | ||
constexpr small_unique_ptr& operator=(small_unique_ptr<U>&& other) noexcept | ||
{ | ||
static_assert(!detail::is_proper_base_of_v<T, U> || std::has_virtual_destructor_v<T>); | ||
|
||
if (!other.is_local() || std::is_constant_evaluated()) | ||
{ | ||
reset(std::exchange(other.data_, nullptr)); | ||
return *this; | ||
} | ||
if constexpr (!always_heap_allocated_v<U>) // other.is_local() | ||
{ | ||
const ptrdiff_t old_offset = offsetof_this(); | ||
reset(); | ||
other.move_buffer_to(*this); | ||
data_ = reinterpret_cast<T*>(reinterpret_cast<unsigned char*>(this->buffer()) + offsetof_base(other) + old_offset); | ||
other.reset(); | ||
} | ||
return *this; | ||
} | ||
|
||
constexpr small_unique_ptr& operator=(std::nullptr_t) noexcept | ||
{ | ||
reset(); | ||
return *this; | ||
} | ||
|
||
constexpr ~small_unique_ptr() noexcept | ||
{ | ||
reset(); | ||
} | ||
|
||
constexpr void reset(pointer new_data = pointer{}) noexcept | ||
{ | ||
is_local() ? std::destroy_at(data_) : delete data_; | ||
data_ = new_data; | ||
if constexpr (!always_heap_allocated_v<T>) this->move_ = nullptr; // maybe add reset() method to buffer? | ||
} | ||
|
||
constexpr void swap(small_unique_ptr& other) noexcept requires(always_heap_allocated_v<T>) | ||
{ | ||
std::swap(data_, other.data_); | ||
} | ||
|
||
constexpr void swap(small_unique_ptr& other) noexcept requires(!always_heap_allocated_v<T>) | ||
{ | ||
if (is_local() && other.is_local()) | ||
{ | ||
T* new_this_data = reinterpret_cast<T*>(reinterpret_cast<unsigned char*>(this->buffer()) + other.offsetof_this()); | ||
T* new_other_data = reinterpret_cast<T*>(reinterpret_cast<unsigned char*>(other.buffer()) + offsetof_this()); | ||
|
||
small_ptr_storage<T> temp; | ||
|
||
other.move_buffer_to(temp); | ||
std::destroy_at(other.buffer()); | ||
this->move_buffer_to(other); | ||
std::destroy_at(this->buffer()); | ||
temp.move_buffer_to(*this); | ||
std::destroy_at(temp.buffer()); | ||
|
||
this->data_ = new_this_data; | ||
other.data_ = new_other_data; | ||
} | ||
else if (!is_local() && !other.is_local()) | ||
{ | ||
std::swap(data_, other.data_); | ||
} | ||
else if (!is_local() && other.is_local()) | ||
{ | ||
T* new_data = reinterpret_cast<T*>(reinterpret_cast<unsigned char*>(this->buffer()) + other.offsetof_this()); | ||
other.move_buffer_to(*this); | ||
other.reset(std::exchange(data_, new_data)); | ||
} | ||
else /* if (is_local() && !other.is_local()) */ | ||
{ | ||
T* new_data = reinterpret_cast<T*>(reinterpret_cast<unsigned char*>(other.buffer()) + offsetof_this()); | ||
this->move_buffer_to(other); | ||
reset(std::exchange(other.data_, new_data)); | ||
} | ||
} | ||
|
||
[[nodiscard]] | ||
constexpr pointer get() const noexcept | ||
{ | ||
return data_; | ||
} | ||
|
||
[[nodiscard]] | ||
constexpr explicit operator bool() const noexcept | ||
{ | ||
return bool(data_); | ||
} | ||
|
||
[[nodiscard]] | ||
constexpr reference operator*() const noexcept(detail::is_nothrow_dereferenceable_v<pointer>) | ||
{ | ||
GAPP_ASSERT(data_); | ||
return *data_; | ||
} | ||
|
||
[[nodiscard]] | ||
constexpr pointer operator->() const noexcept | ||
{ | ||
GAPP_ASSERT(data_); | ||
return data_; | ||
} | ||
|
||
constexpr bool operator==(std::nullptr_t) const noexcept | ||
{ | ||
return data_ == pointer{ nullptr }; | ||
} | ||
|
||
constexpr std::strong_ordering operator<=>(std::nullptr_t) const noexcept | ||
{ | ||
return data_ <=> pointer{ nullptr }; | ||
} | ||
|
||
template<typename U> | ||
constexpr bool operator==(const small_unique_ptr<U>& rhs) const noexcept | ||
{ | ||
return data_ == rhs.data_; | ||
} | ||
|
||
template<typename U> | ||
constexpr std::strong_ordering operator<=>(const small_unique_ptr<U>& rhs) const noexcept | ||
{ | ||
return data_ <=> rhs.data_; | ||
} | ||
|
||
private: | ||
pointer data_ = nullptr; | ||
|
||
constexpr bool is_local() const noexcept requires(!always_heap_allocated_v<T>) { return this->move_ != nullptr; } | ||
constexpr bool is_local() const noexcept requires(always_heap_allocated_v<T>) { return false; } | ||
|
||
template<typename U> | ||
constexpr ptrdiff_t offsetof_base(const small_unique_ptr<U>& other) const noexcept | ||
{ | ||
if constexpr (!detail::is_proper_base_of_v<T, U>) return 0; | ||
else | ||
{ | ||
const auto derived_ptr = reinterpret_cast<const volatile unsigned char*>(other.get()); | ||
const auto base_ptr = reinterpret_cast<const volatile unsigned char*>(static_cast<const volatile T*>(other.get())); | ||
|
||
return base_ptr - derived_ptr; | ||
} | ||
} | ||
|
||
constexpr ptrdiff_t offsetof_this() const noexcept | ||
{ | ||
if (!is_local() || !data_) return 0; | ||
|
||
const auto derived_ptr = reinterpret_cast<const volatile unsigned char*>(this->buffer()); | ||
const auto base_ptr = reinterpret_cast<const volatile unsigned char*>(data_); | ||
|
||
return base_ptr - derived_ptr; | ||
} | ||
|
||
template<typename U> | ||
friend class small_unique_ptr; | ||
|
||
template<typename U, typename... Args> | ||
friend constexpr small_unique_ptr<U> make_unique_small(Args&&...); | ||
}; | ||
|
||
template<typename T> | ||
constexpr void swap(small_unique_ptr<T>& lhs, small_unique_ptr<T>& rhs) noexcept | ||
{ | ||
lhs.swap(rhs); | ||
} | ||
|
||
|
||
template<typename T> | ||
void move_buffer(void* src, void* dst) noexcept | ||
{ | ||
std::construct_at(static_cast<T*>(dst), std::move(*static_cast<T*>(src))); | ||
} | ||
|
||
template<typename T, typename... Args> | ||
[[nodiscard]] constexpr small_unique_ptr<T> make_unique_small(Args&&... args) | ||
{ | ||
small_unique_ptr<T> ptr; | ||
|
||
if constexpr (always_heap_allocated_v<T> || std::is_constant_evaluated()) | ||
{ | ||
ptr.data_ = new T(std::forward<Args>(args)...); | ||
} | ||
else | ||
{ | ||
std::construct_at(ptr.buffer(), std::forward<Args>(args)...); | ||
ptr.data_ = ptr.buffer(); | ||
ptr.move_ = move_buffer<T>; | ||
} | ||
|
||
return ptr; | ||
} | ||
|
||
} // namespace gapp::detail | ||
|
||
#endif // !GA_UTILITY_SMALL_UNIQUE_PTR_HPP |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.