#pragma once #include #include #include "atomic.hpp" namespace stx { // TODO template constexpr bool is_same_ptr_v = true; template constexpr bool is_same_ptr_cast_v = std::is_convertible_v && is_same_ptr_v; template class single_ptr; template class shared_ptr; template class atomic_ptr; // Basic assumption of userspace pointer size constexpr uint c_ptr_size = 47; // Use lower 17 bits as atomic_ptr internal refcounter (pointer is shifted) constexpr uint c_ref_mask = 0x1ffff, c_ref_size = 17; struct shared_counter { // Stored destructor void (*destroy)(void* ptr); // Reference counter atomic_t refs{0}; }; template class unique_data { public: T data; template explicit constexpr unique_data(Args&&... args) noexcept : data(std::forward(args)...) { } }; template class unique_data { std::size_t count; }; // Control block with data and reference counter template class alignas(T) shared_data final : public shared_counter, public unique_data { public: using data_type = T; template explicit constexpr shared_data(Args&&... args) noexcept : shared_counter{} , unique_data(std::forward(args)...) { } }; template class alignas(T) shared_data final : public shared_counter, public unique_data { public: using data_type = T; }; // Simplified unique pointer (well, not simplified, std::unique_ptr is preferred) template class single_ptr { std::remove_extent_t* m_ptr{}; shared_data* d() const noexcept { // Shared counter, deleter, should be at negative offset return std::launder(static_cast*>(reinterpret_cast*>(m_ptr))); } template friend class shared_ptr; template friend class atomic_ptr; public: using pointer = T*; using element_type = std::remove_extent_t; constexpr single_ptr() noexcept = default; constexpr single_ptr(std::nullptr_t) noexcept {} single_ptr(const single_ptr&) = delete; single_ptr(single_ptr&& r) noexcept : m_ptr(r.m_ptr) { r.m_ptr = nullptr; } template >> single_ptr(single_ptr&& r) noexcept : m_ptr(r.m_ptr) { r.m_ptr = nullptr; } ~single_ptr() { reset(); } single_ptr& operator=(const single_ptr&) = delete; single_ptr& operator=(std::nullptr_t) noexcept { reset(); } single_ptr& operator=(single_ptr&& r) noexcept { m_ptr = r.m_ptr; r.m_ptr = nullptr; return *this; } template >> single_ptr& operator=(single_ptr&& r) noexcept { m_ptr = r.m_ptr; r.m_ptr = nullptr; return *this; } void reset() noexcept { if (m_ptr) [[likely]] { d()->destroy(d()); m_ptr = nullptr; } } void swap(single_ptr& r) noexcept { std::swap(m_ptr, r.m_ptr); } element_type* get() const noexcept { return m_ptr; } decltype(auto) operator*() const noexcept { if constexpr (std::is_void_v) { return; } else { return *m_ptr; } } element_type* operator->() const noexcept { return m_ptr; } decltype(auto) operator[](std::ptrdiff_t idx) const noexcept { if constexpr (std::is_void_v) { return; } else if constexpr (std::is_array_v) { return m_ptr[idx]; } else { return *m_ptr; } } explicit constexpr operator bool() const noexcept { return m_ptr != nullptr; } }; template static std::enable_if_t) && (Init || !sizeof...(Args)), single_ptr> make_single(Args&&... args) noexcept { shared_data* ptr = nullptr; if constexpr (Init) { ptr = new shared_data(std::forward(args)...); } else { ptr = new shared_data; } ptr->destroy = [](void* p) { delete static_cast*>(p); }; single_ptr r; reinterpret_cast*&>(r) = &ptr->data; return r; } template static std::enable_if_t, single_ptr> make_single(std::size_t count) noexcept { const std::size_t size = sizeof(shared_data) + count * sizeof(std::remove_extent_t); std::byte* bytes = nullptr; if constexpr (alignof(std::remove_extent_t) > (__STDCPP_DEFAULT_NEW_ALIGNMENT__)) { bytes = new (std::align_val_t{alignof(std::remove_extent_t)}) std::byte[size]; } else { bytes = new std::byte[size]; } // Initialize control block shared_data* ptr = new (reinterpret_cast*>(bytes)) shared_data(); // Initialize array next to the control block T arr = reinterpret_cast(ptr + 1); if constexpr (Init) { std::uninitialized_value_construct_n(arr, count); } else { std::uninitialized_default_construct_n(arr, count); } ptr->m_count = count; ptr->destroy = [](void* p) { shared_data* ptr = static_cast*>(p); std::destroy_n(std::launder(reinterpret_cast(ptr + 1)), ptr->m_count); ptr->~shared_data(); if constexpr (alignof(std::remove_extent_t) > (__STDCPP_DEFAULT_NEW_ALIGNMENT__)) { ::operator delete[](reinterpret_cast(p), std::align_val_t{alignof(std::remove_extent_t)}); } else { delete[] reinterpret_cast(p); } }; single_ptr r; reinterpret_cast*&>(r) = std::launder(arr); return r; } // Simplified shared pointer template class shared_ptr { std::remove_extent_t* m_ptr{}; shared_data* d() const noexcept { // Shared counter, deleter, should be at negative offset return std::launder(static_cast*>(reinterpret_cast*>(m_ptr))); } template friend class atomic_ptr; public: using pointer = T*; using element_type = std::remove_extent_t; constexpr shared_ptr() noexcept = default; constexpr shared_ptr(std::nullptr_t) noexcept {} shared_ptr(const shared_ptr& r) noexcept : m_ptr(r.m_ptr) { if (m_ptr) d()->refs++; } template >> shared_ptr(const shared_ptr& r) noexcept : m_ptr(r.m_ptr) { if (m_ptr) d()->refs++; } shared_ptr(shared_ptr&& r) noexcept : m_ptr(r.m_ptr) { r.m_ptr = nullptr; } template >> shared_ptr(shared_ptr&& r) noexcept : m_ptr(r.m_ptr) { r.m_ptr = nullptr; } template >> shared_ptr(single_ptr&& r) noexcept : m_ptr(r.m_ptr) { r.m_ptr = nullptr; } ~shared_ptr() { reset(); } shared_ptr& operator=(const shared_ptr& r) noexcept { shared_ptr(r).swap(*this); return *this; } template >> shared_ptr& operator=(const shared_ptr& r) noexcept { shared_ptr(r).swap(*this); return *this; } shared_ptr& operator=(shared_ptr&& r) noexcept { shared_ptr(std::move(r)).swap(*this); return *this; } template >> shared_ptr& operator=(shared_ptr&& r) noexcept { shared_ptr(std::move(r)).swap(*this); return *this; } template >> shared_ptr& operator=(single_ptr&& r) noexcept { shared_ptr(std::move(r)).swap(*this); return *this; } // Set to null void reset() noexcept { if (m_ptr && !--d()->refs) [[unlikely]] { d()->destroy(d()); m_ptr = nullptr; } } // Converts to unique (single) ptr if reference is 1, otherwise returns null. Nullifies self. explicit operator single_ptr() && noexcept { if (m_ptr && !--d()->refs) { d()->refs.release(1); return {std::move(*this)}; } m_ptr = nullptr; return {}; } void swap(shared_ptr& r) noexcept { std::swap(this->m_ptr, r.m_ptr); } element_type* get() const noexcept { return m_ptr; } decltype(auto) operator*() const noexcept { if constexpr (std::is_void_v) { return; } else { return *m_ptr; } } element_type* operator->() const noexcept { return m_ptr; } decltype(auto) operator[](std::ptrdiff_t idx) const noexcept { if constexpr (std::is_void_v) { return; } else if constexpr (std::is_array_v) { return m_ptr[idx]; } else { return *m_ptr; } } std::size_t use_count() const noexcept { if (m_ptr) { return d()->refs; } else { return 0; } } explicit constexpr operator bool() const noexcept { return m_ptr != nullptr; } template (std::declval())), typename = std::enable_if_t>> explicit operator shared_ptr() const noexcept { if (m_ptr) { d()->refs++; } shared_ptr r; r.m_ptr = m_ptr; return r; } }; template static std::enable_if_t && (!Init || !sizeof...(Args)), shared_ptr> make_shared(Args&&... args) noexcept { return make_single(std::forward(args)...); } template static std::enable_if_t, shared_ptr> make_shared(std::size_t count) noexcept { return make_single(count); } // Atomic simplified shared pointer template class atomic_ptr { mutable atomic_t m_val{0}; static shared_data* d(uptr val) { return std::launder(static_cast*>(reinterpret_cast*>(val >> c_ref_size))); } shared_data* d() const noexcept { return d(m_val); } public: using pointer = T*; using element_type = std::remove_extent_t; using shared_type = shared_ptr; constexpr atomic_ptr() noexcept = default; constexpr atomic_ptr(std::nullptr_t) noexcept {} explicit atomic_ptr(T value) noexcept { auto r = make_single(std::move(value)); m_val = reinterpret_cast(std::exchange(r.m_ptr, nullptr)) << c_ref_size; d()->refs += c_ref_mask; } template >> atomic_ptr(const shared_ptr& r) noexcept : m_val(reinterpret_cast(r.m_ptr) << c_ref_size) { // Obtain a ref + as many refs as an atomic_ptr can additionally reference if (m_val) d()->refs += c_ref_mask + 1; } template >> atomic_ptr(shared_ptr&& r) noexcept : m_val(reinterpret_cast(r.m_ptr) << c_ref_size) { r.m_ptr = nullptr; if (m_val) d()->refs += c_ref_mask; } template >> atomic_ptr(single_ptr&& r) noexcept : m_val(reinterpret_cast(r.m_ptr) << c_ref_size) { r.m_ptr = nullptr; if (m_val) d()->refs += c_ref_mask; } ~atomic_ptr() { const uptr v = m_val.raw(); if (v && !d(v)->refs.sub_fetch(c_ref_mask + 1 - (v & c_ref_mask))) { d(v)->destroy(d(v)); } } atomic_ptr& operator=(T value) noexcept { // TODO: does it make sense? store(make_single(std::move(value))); return *this; } template >> atomic_ptr& operator=(const shared_ptr& r) noexcept { store(r); return *this; } template >> atomic_ptr& operator=(shared_ptr&& r) noexcept { store(std::move(r)); return *this; } template >> atomic_ptr& operator=(single_ptr&& r) noexcept { store(std::move(r)); return *this; } shared_type load() const noexcept { shared_type r; // Add reference const auto [prev, did_ref] = m_val.fetch_op([](uptr& val) { if (val >> c_ref_size) { val++; return true; } return false; }); if (!did_ref) { // Null pointer return r; } // Set referenced pointer r.m_ptr = std::launder(reinterpret_cast(prev >> c_ref_size)); // Dereference if same pointer m_val.fetch_op([prev = prev](uptr& val) { if (val >> c_ref_size == prev >> c_ref_size) { val--; return true; } return false; }); return r; } void store(T value) noexcept { store(make_single(std::move(value))); } void store(shared_type value) noexcept { if (value.m_ptr) { // Consume value and add refs value.d()->refs += c_ref_mask; } atomic_ptr old; old.m_val.raw() = m_val.exchange(reinterpret_cast(std::exchange(value.m_ptr, nullptr)) << c_ref_size); } [[nodiscard]] shared_type exchange(shared_type value) noexcept { atomic_ptr old; if (value.m_ptr) { // Consume value and add refs value.d()->refs += c_ref_mask; old.m_val.raw() += 1; } old.m_val.raw() += m_val.exchange(reinterpret_cast(std::exchange(value.m_ptr, nullptr)) << c_ref_size); shared_type r; r.m_ptr = old.m_val >> c_ref_size; return r; } // Simple atomic load is much more effective than load(), but it's a non-owning reference const volatile void* observe() const noexcept { return reinterpret_cast(m_val >> c_ref_size); } explicit constexpr operator bool() const noexcept { return m_val != 0; } bool is_equal(const shared_ptr& r) const noexcept { return observe() == r.get(); } bool is_equal(const single_ptr& r) const noexcept { return observe() == r.get(); } }; } namespace std { template void swap(stx::single_ptr& lhs, stx::single_ptr& rhs) noexcept { lhs.swap(rhs); } template void swap(stx::shared_ptr& lhs, stx::shared_ptr& rhs) noexcept { lhs.swap(rhs); } } using stx::single_ptr; using stx::shared_ptr; using stx::atomic_ptr; using stx::make_single;