diff options
Diffstat (limited to 'src/zenbase/include')
| -rw-r--r-- | src/zenbase/include/zenbase/atomic.h | 74 | ||||
| -rw-r--r-- | src/zenbase/include/zenbase/refcount.h | 157 |
2 files changed, 62 insertions, 169 deletions
diff --git a/src/zenbase/include/zenbase/atomic.h b/src/zenbase/include/zenbase/atomic.h deleted file mode 100644 index 4ad7962cf..000000000 --- a/src/zenbase/include/zenbase/atomic.h +++ /dev/null @@ -1,74 +0,0 @@ -// Copyright Epic Games, Inc. All Rights Reserved. - -#pragma once - -#include <zenbase/zenbase.h> - -#if ZEN_COMPILER_MSC -# include <intrin.h> -#else -# include <atomic> -#endif - -#include <cinttypes> - -namespace zen { - -inline uint32_t -AtomicIncrement(volatile uint32_t& value) -{ -#if ZEN_COMPILER_MSC - return _InterlockedIncrement((long volatile*)&value); -#else - return ((std::atomic<uint32_t>*)(&value))->fetch_add(1, std::memory_order_seq_cst) + 1; -#endif -} -inline uint32_t -AtomicDecrement(volatile uint32_t& value) -{ -#if ZEN_COMPILER_MSC - return _InterlockedDecrement((long volatile*)&value); -#else - return ((std::atomic<uint32_t>*)(&value))->fetch_sub(1, std::memory_order_seq_cst) - 1; -#endif -} - -inline uint64_t -AtomicIncrement(volatile uint64_t& value) -{ -#if ZEN_COMPILER_MSC - return _InterlockedIncrement64((__int64 volatile*)&value); -#else - return ((std::atomic<uint64_t>*)(&value))->fetch_add(1, std::memory_order_seq_cst) + 1; -#endif -} -inline uint64_t -AtomicDecrement(volatile uint64_t& value) -{ -#if ZEN_COMPILER_MSC - return _InterlockedDecrement64((__int64 volatile*)&value); -#else - return ((std::atomic<uint64_t>*)(&value))->fetch_sub(1, std::memory_order_seq_cst) - 1; -#endif -} - -inline uint32_t -AtomicAdd(volatile uint32_t& value, uint32_t amount) -{ -#if ZEN_COMPILER_MSC - return _InterlockedExchangeAdd((long volatile*)&value, amount); -#else - return ((std::atomic<uint32_t>*)(&value))->fetch_add(amount, std::memory_order_seq_cst); -#endif -} -inline uint64_t -AtomicAdd(volatile uint64_t& value, uint64_t amount) -{ -#if ZEN_COMPILER_MSC - return _InterlockedExchangeAdd64((__int64 volatile*)&value, amount); -#else - return ((std::atomic<uint64_t>*)(&value))->fetch_add(amount, std::memory_order_seq_cst); -#endif -} - -} // namespace zen diff --git a/src/zenbase/include/zenbase/refcount.h b/src/zenbase/include/zenbase/refcount.h index 08bc6ae54..0da78ad91 100644 --- a/src/zenbase/include/zenbase/refcount.h +++ b/src/zenbase/include/zenbase/refcount.h @@ -1,9 +1,9 @@ // Copyright Epic Games, Inc. All Rights Reserved. #pragma once -#include <zenbase/atomic.h> #include <zenbase/concepts.h> +#include <atomic> #include <compare> namespace zen { @@ -13,6 +13,13 @@ namespace zen { * * When the reference count reaches zero, the object deletes itself. This class relies * on having a virtual destructor to ensure proper cleanup of derived classes. + * + * Release() is marked noexcept. Derived destructors are expected not to throw - if a + * derived destructor does throw, the exception crosses the noexcept boundary and the + * program terminates via std::terminate. This matches C++'s default destructor + * behaviour (destructors are implicitly noexcept) but is spelled out explicitly here + * because the delete happens inside Release() rather than at the call site, so the + * terminate point is not visually obvious. */ class RefCounted { @@ -20,10 +27,15 @@ public: RefCounted() = default; virtual ~RefCounted() = default; - inline uint32_t AddRef() const noexcept { return AtomicIncrement(const_cast<RefCounted*>(this)->m_RefCount); } - inline uint32_t Release() const + // AddRef uses relaxed ordering: a thread can only add a reference to an object it + // already has a reference to, so there is nothing that needs to synchronize here. + // Release uses acq_rel so that (a) all prior modifications of the object made on + // other threads happen-before the destructor, and (b) the ref-count decrement is + // visible to any thread that observes the new count. + inline uint32_t AddRef() const noexcept { return m_RefCount.fetch_add(1, std::memory_order_relaxed) + 1; } + inline uint32_t Release() const noexcept { - const uint32_t RefCount = AtomicDecrement(const_cast<RefCounted*>(this)->m_RefCount); + const uint32_t RefCount = m_RefCount.fetch_sub(1, std::memory_order_acq_rel) - 1; if (RefCount == 0) { delete this; @@ -39,10 +51,12 @@ public: RefCounted& operator=(RefCounted&&) = delete; protected: - inline uint32_t RefCount() const { return m_RefCount; } + // Diagnostic accessor: relaxed load is fine because the returned value is already + // stale the moment it is observed, so no extra ordering would make it more reliable. + inline uint32_t RefCount() const noexcept { return m_RefCount.load(std::memory_order_relaxed); } private: - uint32_t m_RefCount = 0; + mutable std::atomic<uint32_t> m_RefCount = 0; }; /** @@ -52,6 +66,10 @@ private: * It has no virtual destructor, so it's important that you either don't derive from it further, * or ensure that the derived class has a virtual destructor. * + * As with RefCounted, Release() is noexcept and the derived destructor must not throw. + * A throwing destructor will cause std::terminate to be called when the refcount hits + * zero. + * * This class is useful when you want to avoid adding a vtable to a class just to implement * reference counting. */ @@ -60,15 +78,17 @@ template<typename T> class TRefCounted { public: - TRefCounted() = default; - ~TRefCounted() = default; + TRefCounted() = default; - inline uint32_t AddRef() const noexcept { return AtomicIncrement(const_cast<TRefCounted<T>*>(this)->m_RefCount); } - inline uint32_t Release() const + // See RefCounted::AddRef/Release for ordering rationale. + inline uint32_t AddRef() const noexcept { return m_RefCount.fetch_add(1, std::memory_order_relaxed) + 1; } + inline uint32_t Release() const noexcept { - const uint32_t RefCount = AtomicDecrement(const_cast<TRefCounted<T>*>(this)->m_RefCount); + const uint32_t RefCount = m_RefCount.fetch_sub(1, std::memory_order_acq_rel) - 1; if (RefCount == 0) { + // DeleteThis may be overridden as a non-const member in derived types, + // so we cast away const to support both signatures. const_cast<T*>(static_cast<const T*>(this))->DeleteThis(); } return RefCount; @@ -82,92 +102,21 @@ public: TRefCounted& operator=(TRefCounted&&) = delete; protected: - inline uint32_t RefCount() const { return m_RefCount; } - - void DeleteThis() const noexcept { delete static_cast<const T*>(this); } - -private: - uint32_t m_RefCount = 0; -}; - -/** - * Smart pointer for classes derived from RefCounted - */ - -template<class T> -class RefPtr -{ -public: - inline RefPtr() = default; - inline RefPtr(const RefPtr& Rhs) : m_Ref(Rhs.m_Ref) { m_Ref && m_Ref->AddRef(); } - inline RefPtr(T* Ptr) : m_Ref(Ptr) { m_Ref && m_Ref->AddRef(); } - inline ~RefPtr() { m_Ref && m_Ref->Release(); } - - [[nodiscard]] inline bool IsNull() const { return m_Ref == nullptr; } - inline explicit operator bool() const { return m_Ref != nullptr; } - inline operator T*() const { return m_Ref; } - inline T* operator->() const { return m_Ref; } + // Non-virtual: destruction goes through DeleteThis(), which deletes the derived type. + // Protected so that external callers cannot `delete` through a TRefCounted<T>* and slice. + ~TRefCounted() = default; - inline std::strong_ordering operator<=>(const RefPtr& Rhs) const = default; + // Diagnostic accessor: see RefCounted::RefCount for ordering rationale. + inline uint32_t RefCount() const noexcept { return m_RefCount.load(std::memory_order_relaxed); } - inline RefPtr& operator=(T* Rhs) - { - Rhs && Rhs->AddRef(); - m_Ref && m_Ref->Release(); - m_Ref = Rhs; - return *this; - } - inline RefPtr& operator=(const RefPtr& Rhs) - { - if (&Rhs != this) - { - Rhs && Rhs->AddRef(); - m_Ref && m_Ref->Release(); - m_Ref = Rhs.m_Ref; - } - return *this; - } - inline RefPtr& operator=(RefPtr&& Rhs) noexcept - { - if (&Rhs != this) - { - m_Ref && m_Ref->Release(); - m_Ref = Rhs.m_Ref; - Rhs.m_Ref = nullptr; - } - return *this; - } - template<typename OtherType> - inline RefPtr& operator=(RefPtr<OtherType>&& Rhs) noexcept - { - if ((RefPtr*)&Rhs != this) - { - m_Ref && m_Ref->Release(); - m_Ref = Rhs.m_Ref; - Rhs.m_Ref = nullptr; - } - return *this; - } - inline RefPtr(RefPtr&& Rhs) noexcept : m_Ref(Rhs.m_Ref) { Rhs.m_Ref = nullptr; } - template<typename OtherType> - explicit inline RefPtr(RefPtr<OtherType>&& Rhs) noexcept : m_Ref(Rhs.m_Ref) - { - Rhs.m_Ref = nullptr; - } - - inline void Swap(RefPtr& Rhs) noexcept { std::swap(m_Ref, Rhs.m_Ref); } + void DeleteThis() const noexcept { delete static_cast<const T*>(this); } private: - T* m_Ref = nullptr; - template<typename U> - friend class RefPtr; + mutable std::atomic<uint32_t> m_RefCount = 0; }; /** - * Smart pointer for classes derived from RefCounted - * - * This variant does not decay to a raw pointer - * + * Smart pointer for classes derived from RefCounted (or TRefCounted) */ template<class T> @@ -177,12 +126,16 @@ public: inline Ref() = default; inline Ref(Ref&& Rhs) noexcept : m_Ref(Rhs.m_Ref) { Rhs.m_Ref = nullptr; } inline Ref(const Ref& Rhs) noexcept : m_Ref(Rhs.m_Ref) { m_Ref && m_Ref->AddRef(); } - inline explicit Ref(T* Ptr) : m_Ref(Ptr) { m_Ref && m_Ref->AddRef(); } - inline ~Ref() { m_Ref && m_Ref->Release(); } + inline explicit Ref(T* Ptr) noexcept : m_Ref(Ptr) { m_Ref && m_Ref->AddRef(); } + inline ~Ref() noexcept { m_Ref && m_Ref->Release(); } template<typename DerivedType> requires DerivedFrom<DerivedType, T> - inline Ref(const Ref<DerivedType>& Rhs) : Ref(Rhs.m_Ref) {} + inline Ref(const Ref<DerivedType>& Rhs) noexcept : Ref(Rhs.m_Ref) {} + + template<typename DerivedType> + requires DerivedFrom<DerivedType, T> + inline Ref(Ref<DerivedType>&& Rhs) noexcept : m_Ref(Rhs.m_Ref) { Rhs.m_Ref = nullptr; } [[nodiscard]] inline bool IsNull() const { return m_Ref == nullptr; } inline explicit operator bool() const { return m_Ref != nullptr; } @@ -192,14 +145,14 @@ public: inline std::strong_ordering operator<=>(const Ref& Rhs) const = default; - inline Ref& operator=(T* Rhs) + inline Ref& operator=(T* Rhs) noexcept { Rhs && Rhs->AddRef(); m_Ref && m_Ref->Release(); m_Ref = Rhs; return *this; } - inline Ref& operator=(const Ref& Rhs) + inline Ref& operator=(const Ref& Rhs) noexcept { if (&Rhs != this) { @@ -219,6 +172,20 @@ public: } return *this; } + template<typename DerivedType> + requires DerivedFrom<DerivedType, T> + inline Ref& operator=(Ref<DerivedType>&& Rhs) noexcept + { + if ((Ref*)&Rhs != this) + { + m_Ref && m_Ref->Release(); + m_Ref = Rhs.m_Ref; + Rhs.m_Ref = nullptr; + } + return *this; + } + + inline void Swap(Ref& Rhs) noexcept { std::swap(m_Ref, Rhs.m_Ref); } private: T* m_Ref = nullptr; |