diff options
| -rw-r--r-- | libs/kstd/kstd/bits/shared_ptr.hpp | 286 |
1 files changed, 192 insertions, 94 deletions
diff --git a/libs/kstd/kstd/bits/shared_ptr.hpp b/libs/kstd/kstd/bits/shared_ptr.hpp index 03c107c0..c82071a2 100644 --- a/libs/kstd/kstd/bits/shared_ptr.hpp +++ b/libs/kstd/kstd/bits/shared_ptr.hpp @@ -2,6 +2,7 @@ #define KSTD_BITS_SHARED_PTR_HPP #include <atomic> +#include <concepts> #include <cstddef> #include <type_traits> #include <utility> @@ -10,51 +11,85 @@ namespace kstd { - /** - * @brief Control block for shared_ptr and weak_ptr. This control block contains the reference counts for shared - * ownership and weak ownership. The shared_count tracks the number of shared_ptr instances that own the managed - * object, while the weak_count tracks the number of weak_ptr instances that reference the managed object. - * The weak_count is needed to determine when it is safe to delete the shared_control_block itself - */ - struct shared_control_block + + namespace bits { - std::atomic<std::size_t> shared_count; - std::atomic<std::size_t> weak_count; + //! The control block used by shared pointers and weak pointers. + //! + //! @see shared_ptr + //! @see weak_ptr + struct shared_control_block + { + //! The number of strong references to the managed object. + std::atomic<std::size_t> strong_count; + //! The number of weak references to the managed object. + std::atomic<std::size_t> weak_count; - explicit shared_control_block(std::size_t shared = 1, std::size_t weak = 1) - : shared_count(shared) - , weak_count(weak) - {} - }; + //! Construct a new control block with a given number of references. + //! + //! @param shared The number of strong references. + //! @param weak The number of weak references. + explicit shared_control_block(std::size_t shared = 1, std::size_t weak = 1) + : strong_count(shared) + , weak_count(weak) + {} + }; + + template<typename Y, typename T> + struct is_shared_pointer_ctor_compatible : std::false_type + { + }; + + template<typename Y, typename T> + struct is_shared_pointer_ctor_compatible<Y *, T *> : std::is_convertible<Y *, T *> + { + }; + + template<typename U, std::size_t N> + struct is_shared_pointer_ctor_compatible<U (*)[N], U (*)[]> // NOLINT(modernize-avoid-c-arrays) + : std::true_type + { + }; + + template<typename U, std::size_t N> + struct is_shared_pointer_ctor_compatible<U (*)[N], U const (*)[]> // NOLINT(modernize-avoid-c-arrays) + : std::true_type + { + }; + + template<typename U, std::size_t N> + struct is_shared_pointer_ctor_compatible<U (*)[N], U volatile (*)[]> // NOLINT(modernize-avoid-c-arrays) + : std::true_type + { + }; + + template<typename U, std::size_t N> + struct is_shared_pointer_ctor_compatible<U (*)[N], U volatile const (*)[]> // NOLINT(modernize-avoid-c-arrays) + : std::true_type + { + }; + } // namespace bits template<typename T> struct shared_ptr; - /** - * @brief weak_ptr is a smart pointer that holds a non-owning weak reference to an object that is managed by - * shared_ptr. It must be converted to shared_ptr in to be able to access the referenced object. A weak_ptr is created - * as a copy of a shared_ptr, or as a copy of another weak_ptr. A weak_ptr is typically used to break circular - * references between shared_ptr to avoid memory leaks. A weak_ptr does not contribute to the reference count of the - * object, and it does not manage the lifetime of the object. If the object managed by shared_ptr is destroyed, the - * weak_ptr becomes expired and cannot be used to access the object anymore. - */ + //! A pointer type to hold a non-owning reference to an object managed by a share pointer. + //! + //! @tparam T The type of the object referenced by this weak pointer. template<typename T> struct weak_ptr { template<typename U> friend struct shared_ptr; - /** - * @brief Constructs a null weak_ptr. - */ - weak_ptr() noexcept + //! Construct an empty weak pointer. + constexpr weak_ptr() noexcept : m_storage(nullptr) , m_control_block(nullptr) {} - template<typename U> - requires(std::is_convertible_v<U *, T *>) - weak_ptr(shared_ptr<U> const & other) + //! Construct a weak pointer which shares ownership of the object managed by other. + constexpr weak_ptr(weak_ptr const & other) noexcept : m_storage(other.m_storage) , m_control_block(other.m_control_block) { @@ -64,10 +99,10 @@ namespace kstd } } - /** - * @brief Copy constructor. Constructs a weak_ptr which shares ownership of the object managed by other. - */ - weak_ptr(weak_ptr const & other) + //! Construct a weak pointer which shares ownership of the object managed by other. + template<typename Y> + requires bits::is_shared_pointer_ctor_compatible<Y *, T *>::value + constexpr weak_ptr(weak_ptr<Y> const & other) : m_storage(other.m_storage) , m_control_block(other.m_control_block) { @@ -77,86 +112,149 @@ namespace kstd } } - /** - * @brief Move constructor. Constructs a weak_ptr which takes ownership of the object managed by other. - */ - weak_ptr(weak_ptr && other) noexcept + //! Construct a weak pointer which shares ownership of the object managed by other. + template<typename Y> + requires bits::is_shared_pointer_ctor_compatible<Y *, T *>::value + constexpr weak_ptr(shared_ptr<Y> const & other) : m_storage(other.m_storage) , m_control_block(other.m_control_block) { - other.m_storage = nullptr; - other.m_control_block = nullptr; - } - - /** - * @brief Assignment operator. Assigns the weak_ptr to another weak_ptr. - */ - auto operator=(weak_ptr const & other) -> weak_ptr & - { - if (this != &other) + if (m_control_block != nullptr) { - cleanup(); - m_storage = other.m_storage; - m_control_block = other.m_control_block; - if (m_control_block != nullptr) - { - ++(m_control_block->weak_count); - } + ++(m_control_block->weak_count); } + } + + //! Construct a weak pointer which takes ownership of the object managed by other. + constexpr weak_ptr(weak_ptr && other) noexcept + : m_storage(std::exchange(other.m_storage, nullptr)) + , m_control_block(std::exchange(other.m_control_block, nullptr)) + {} + + //! Construct a weak pointer which takes ownership of the object managed by other. + template<typename Y> + requires std::is_convertible_v<Y *, T *> + constexpr weak_ptr(weak_ptr<Y> && other) noexcept + : m_storage(std::exchange(other.m_storage, nullptr)) + , m_control_block(std::exchange(other.m_control_block, nullptr)) + {} + //! Replace the object managed by this pointer with the one managed by another one. + auto operator=(weak_ptr const & other) noexcept -> weak_ptr & + { + weak_ptr<T>{other}.swap(*this); return *this; } - /** - * @brief Move assignment operator. Move-assigns a weak_ptr from other. - */ - auto operator=(weak_ptr && other) noexcept -> weak_ptr & + //! Replace the object managed by this pointer with the one managed by another one. + template<typename Y> + constexpr auto operator=(weak_ptr<Y> const & other) noexcept -> weak_ptr & { - if (this != &other) - { - cleanup(); - m_storage = other.m_storage; - m_control_block = other.m_control_block; - other.m_storage = nullptr; - other.m_control_block = nullptr; - } + weak_ptr<T>{other}.swap(*this); + return *this; + } + //! Replace the object managed by this pointer with the one managed by shared pointer. + template<typename Y> + constexpr auto operator=(shared_ptr<Y> const & other) noexcept -> weak_ptr & + { + weak_ptr<T>{other}.swap(*this); return *this; } - /** - * @brief Destructor. Cleans up resources if necessary. - */ - ~weak_ptr() + //! Replace the object managed by this pointer with the one managed by another one. + auto operator=(weak_ptr && other) noexcept -> weak_ptr & { - cleanup(); + weak_ptr<T>{std::move(other)}.swap(*this); + return *this; } - /** - * @brief Returns a shared_ptr that shares ownership of the managed object if the managed object still exists, or an - * empty shared_ptr otherwise. - */ - [[nodiscard]] auto lock() const -> shared_ptr<T> + //! Replace the object managed by this pointer with the one managed by another one. + template<typename Y> + auto operator=(weak_ptr<Y> && other) noexcept -> weak_ptr & { - return shared_ptr<T>(*this); + weak_ptr<T>{std::move(other)}.swap(*this); + return *this; } - private: - auto cleanup() -> void + //! Destroy this pointer, cleaning up resources if necessary. + ~weak_ptr() { - if (m_control_block != nullptr) + if (m_control_block) { - if (--(m_control_block->weak_count) == 0) + if (!m_control_block->weak_count.fetch_sub(1)) { delete m_control_block; + m_control_block = nullptr; + m_storage = nullptr; } } } + //! Release the reference to the object managed by this weak pointer. + constexpr auto reset() -> void + { + weak_ptr<T>{}.swap(*this); + } + + //! Exchange the ownership of the object managed by this pointer with the one of another one. + constexpr auto swap(weak_ptr & other) -> void + { + std::ranges::swap(m_storage, other.m_storage); + std::ranges::swap(m_control_block, other.m_control_block); + } + + //! Exchange the ownership of the object managed by weak pointer objects. + constexpr auto friend swap(weak_ptr & lhs, weak_ptr & rhs) noexcept -> void + { + return lhs.swap(rhs); + } + + //! Get the number of shared pointers that share ownership with the object managed by this weak pointer. + [[nodiscard]] constexpr auto use_count() const noexcept -> long + { + if (m_control_block) + { + return m_control_block->strong_count; + } + return 0; + } + + //! Check if the object managed by this weak pointer has already been deleted. + [[nodiscard]] constexpr auto expired() const noexcept -> bool + { + return use_count() == 0; + } + + //! Create a shared pointer that manages the object, if any, managed by this weak pointer. + [[nodiscard]] auto lock() const -> shared_ptr<T> + { + return expired() ? shared_ptr<T>{} : shared_ptr<T>(*this); + } + + //! Check if this weak pointer precedes another one in owner-based order. + template<typename Y> + [[nodiscard]] auto owner_before(weak_ptr<Y> const & other) const noexcept -> bool + { + return m_control_block < other.m_control_block; + } + + //! Check if this weak pointer precedes a shared pointer in owner-based order. + template<typename Y> + [[nodiscard]] auto owner_before(shared_ptr<Y> const & other) const noexcept -> bool + { + return m_control_block < other.m_control_block; + } + + private: T * m_storage; - shared_control_block * m_control_block; + bits::shared_control_block * m_control_block; }; + //! Deduction guide for construction from a shared pointer. + template<typename T> + weak_ptr(shared_ptr<T>) -> weak_ptr<T>; + /** * @brief enable_shared_from_this is a base class that allows an object that is currently managed by a shared_ptr to * create additional shared_ptr instances that share ownership of the same object. This is usefl when you want to @@ -249,7 +347,7 @@ namespace kstd requires(std::is_convertible_v<U *, T *>) explicit shared_ptr(U * pointer = nullptr) : m_storage(pointer) - , m_control_block(pointer != nullptr ? new shared_control_block() : nullptr) + , m_control_block(pointer != nullptr ? new bits::shared_control_block() : nullptr) { assign_enable_shared_from_this(pointer); } @@ -268,8 +366,8 @@ namespace kstd { if (other.m_control_block) { - auto count = other.m_control_block->shared_count.load(std::memory_order::relaxed); - while (count != 0 && !other.m_control_block->shared_count.compare_exchange_weak( + auto count = other.m_control_block->strong_count.load(std::memory_order::relaxed); + while (count != 0 && !other.m_control_block->strong_count.compare_exchange_weak( count, count + 1, std::memory_order::acquire, std::memory_order::relaxed)) ; @@ -292,7 +390,7 @@ namespace kstd { if (m_control_block != nullptr) { - ++(m_control_block->shared_count); + ++(m_control_block->strong_count); } } @@ -310,7 +408,7 @@ namespace kstd { if (m_control_block != nullptr) { - ++(m_control_block->shared_count); + ++(m_control_block->strong_count); } } @@ -361,7 +459,7 @@ namespace kstd if (m_control_block != nullptr) { - ++(m_control_block->shared_count); + ++(m_control_block->strong_count); } } @@ -385,7 +483,7 @@ namespace kstd if (m_control_block != nullptr) { - ++(m_control_block->shared_count); + ++(m_control_block->strong_count); } return *this; @@ -460,7 +558,7 @@ namespace kstd { cleanup(); m_storage = ptr; - m_control_block = ptr != nullptr ? new shared_control_block() : nullptr; + m_control_block = ptr != nullptr ? new bits::shared_control_block() : nullptr; assign_enable_shared_from_this(ptr); } @@ -521,7 +619,7 @@ namespace kstd { if (m_control_block != nullptr) { - return m_control_block->shared_count; + return m_control_block->strong_count; } return 0; @@ -581,7 +679,7 @@ namespace kstd { if (m_control_block != nullptr) { - if (--(m_control_block->shared_count) == 0) + if (--(m_control_block->strong_count) == 0) { delete m_storage; m_storage = nullptr; @@ -594,8 +692,8 @@ namespace kstd } } - T * m_storage; ///< The managed object. - shared_control_block * m_control_block; ///< Shared control block. + T * m_storage; ///< The managed object. + bits::shared_control_block * m_control_block; ///< Shared control block. }; /** |
