#pragma once #include #include #include #include #include namespace xrpl { //------------------------------------------------------------------------------ /** * Tag to create an intrusive pointer from another intrusive pointer by using a * static cast. This is useful to create an intrusive pointer to a derived * class from an intrusive pointer to a base class. */ struct StaticCastTagSharedIntrusive { }; /** * Tag to create an intrusive pointer from another intrusive pointer by using a * dynamic cast. This is useful to create an intrusive pointer to a derived * class from an intrusive pointer to a base class. If the cast fails an empty * (null) intrusive pointer is created. */ struct DynamicCastTagSharedIntrusive { }; /** * When creating or adopting a raw pointer, controls whether the strong count * is incremented or not. Use this tag to increment the strong count. */ struct SharedIntrusiveAdoptIncrementStrongTag { }; /** * When creating or adopting a raw pointer, controls whether the strong count * is incremented or not. Use this tag to leave the strong count unchanged. */ struct SharedIntrusiveAdoptNoIncrementTag { }; //------------------------------------------------------------------------------ // template concept CAdoptTag = std::is_same_v || std::is_same_v; //------------------------------------------------------------------------------ /** * A shared intrusive pointer class that supports weak pointers. * * This is meant to be used for SHAMapInnerNodes, but may be useful for other * cases. Since the reference counts are stored on the pointee, the pointee is * not destroyed until both the strong _and_ weak pointer counts go to zero. * When the strong pointer count goes to zero, the "partialDestructor" is * called. This can be used to destroy as much of the object as possible while * still retaining the reference counts. For example, for SHAMapInnerNodes the * children may be reset in that function. Note that std::shared_pointer WILL * run the destructor when the strong count reaches zero, but may not free the * memory used by the object until the weak count reaches zero. In xrpld, we * typically allocate shared pointers with the `make_shared` function. When * that is used, the memory is not reclaimed until the weak count reaches zero. */ template class SharedIntrusive { public: SharedIntrusive() = default; template SharedIntrusive(T* p, TAdoptTag) noexcept; SharedIntrusive(SharedIntrusive const& rhs); template // TODO: convertible_to isn't quite right. That include a static castable. // Find the right concept. requires std::convertible_to SharedIntrusive(SharedIntrusive const& rhs); SharedIntrusive(SharedIntrusive&& rhs); template requires std::convertible_to SharedIntrusive( SharedIntrusive&& rhs); // NOLINT(cppcoreguidelines-rvalue-reference-param-not-moved) SharedIntrusive& operator=(SharedIntrusive const& rhs); bool operator!=(std::nullptr_t) const; bool operator==(std::nullptr_t) const; template requires std::convertible_to SharedIntrusive& operator=(SharedIntrusive const& rhs); SharedIntrusive& operator=(SharedIntrusive&& rhs); template requires std::convertible_to SharedIntrusive& operator=( SharedIntrusive&& rhs); // NOLINT(cppcoreguidelines-rvalue-reference-param-not-moved) /** * Adopt the raw pointer. The strong reference may or may not be * incremented, depending on the TAdoptTag */ template void adopt(T* p); ~SharedIntrusive(); /** * Create a new SharedIntrusive by statically casting the pointer * controlled by the rhs param. */ template SharedIntrusive(StaticCastTagSharedIntrusive, SharedIntrusive const& rhs); /** * Create a new SharedIntrusive by statically casting the pointer * controlled by the rhs param. */ template SharedIntrusive(StaticCastTagSharedIntrusive, SharedIntrusive&& rhs); /** * Create a new SharedIntrusive by dynamically casting the pointer * controlled by the rhs param. */ template SharedIntrusive(DynamicCastTagSharedIntrusive, SharedIntrusive const& rhs); /** * Create a new SharedIntrusive by dynamically casting the pointer * controlled by the rhs param. */ template SharedIntrusive(DynamicCastTagSharedIntrusive, SharedIntrusive&& rhs); T& operator*() const noexcept; T* operator->() const noexcept; explicit operator bool() const noexcept; /** * Set the pointer to null, decrement the strong count, and run the * appropriate release action. */ void reset(); /** * Get the raw pointer */ [[nodiscard]] T* get() const; /** * Return the strong count */ [[nodiscard]] std::size_t useCount() const; template friend SharedIntrusive makeSharedIntrusive(Args&&... args); template friend class SharedIntrusive; template friend class SharedWeakUnion; template friend class WeakIntrusive; private: /** * Return the raw pointer held by this object. */ [[nodiscard]] T* unsafeGetRawPtr() const; /** * Exchange the current raw pointer held by this object with the given * pointer. Decrement the strong count of the raw pointer previously held * by this object and run the appropriate release action. */ void unsafeReleaseAndStore(T* next); /** * Set the raw pointer directly. This is wrapped in a function so the class * can support both atomic and non-atomic pointers in a future patch. */ void unsafeSetRawPtr(T* p); /** * Exchange the raw pointer directly. * This sets the raw pointer to the given value and returns the previous * value. This is wrapped in a function so the class can support both * atomic and non-atomic pointers in a future patch. */ T* unsafeExchange(T* p); /** * pointer to the type with an intrusive count */ T* ptr_{nullptr}; }; //------------------------------------------------------------------------------ /** * A weak intrusive pointer class for the SharedIntrusive pointer class. * * Note that this weak pointer class asks differently from normal weak pointer * classes. When the strong pointer count goes to zero, the "partialDestructor" * is called. See the comment on SharedIntrusive for a fuller explanation. */ template class WeakIntrusive { public: WeakIntrusive() = default; WeakIntrusive(WeakIntrusive const& rhs); WeakIntrusive(WeakIntrusive&& rhs); WeakIntrusive(SharedIntrusive const& rhs); // There is no move constructor from a strong intrusive ptr because // moving would be move expensive than copying in this case (the strong // ref would need to be decremented) WeakIntrusive(SharedIntrusive const&& rhs) = delete; // Since there are no current use cases for copy assignment in // WeakIntrusive, we delete this operator to simplify the implementation. If // a need arises in the future, we can reintroduce it with proper // consideration." WeakIntrusive& operator=(WeakIntrusive const&) = delete; template requires std::convertible_to WeakIntrusive& operator=(SharedIntrusive const& rhs); /** * Adopt the raw pointer and increment the weak count. */ void adopt(T* ptr); ~WeakIntrusive(); /** * Get a strong pointer from the weak pointer, if possible. This will * only return a seated pointer if the strong count on the raw pointer * is non-zero before locking. */ SharedIntrusive lock() const; /** * Return true if the strong count is zero. */ [[nodiscard]] bool expired() const; /** * Set the pointer to null and decrement the weak count. * * Note: This may run the destructor if the strong count is zero. */ void reset(); private: T* ptr_ = nullptr; /** * Decrement the weak count. This does _not_ set the raw pointer to * null. * * Note: This may run the destructor if the strong count is zero. */ void unsafeReleaseNoStore(); }; //------------------------------------------------------------------------------ /** * A combination of a strong and a weak intrusive pointer stored in the * space of a single pointer. * * This class is similar to a `std::variant` * with some optimizations. In particular, it uses a low-order bit to * determine if the raw pointer represents a strong pointer or a weak * pointer. It can also be quickly switched between its strong pointer and * weak pointer representations. This class is useful for storing intrusive * pointers in tagged caches. */ template class SharedWeakUnion { // Tagged pointer. Low bit determines if this is a strong or a weak // pointer. The low bit must be masked to zero when converting back to a // pointer. If the low bit is '1', this is a weak pointer. static_assert(alignof(T) >= 2, "Bad alignment: Combo pointer requires low bit to be zero"); public: SharedWeakUnion() = default; SharedWeakUnion(SharedWeakUnion const& rhs); template requires std::convertible_to SharedWeakUnion(SharedIntrusive const& rhs); SharedWeakUnion(SharedWeakUnion&& rhs); template requires std::convertible_to SharedWeakUnion( SharedIntrusive&& rhs); // NOLINT(cppcoreguidelines-rvalue-reference-param-not-moved) SharedWeakUnion& operator=(SharedWeakUnion const& rhs); template requires std::convertible_to SharedWeakUnion& operator=(SharedIntrusive const& rhs); template requires std::convertible_to SharedWeakUnion& operator=( SharedIntrusive&& rhs); // NOLINT(cppcoreguidelines-rvalue-reference-param-not-moved) ~SharedWeakUnion(); /** * Return a strong pointer if this is already a strong pointer (i.e. * don't lock the weak pointer. Use the `lock` method if that's what's * needed) */ [[nodiscard]] SharedIntrusive getStrong() const; /** * Return true if this is a strong pointer and the strong pointer is * seated. */ explicit operator bool() const noexcept; /** * Set the pointer to null, decrement the appropriate ref count, and * run the appropriate release action. */ void reset(); /** * If this is a strong pointer, return the raw pointer. Otherwise * return null. */ [[nodiscard]] T* get() const; /** * If this is a strong pointer, return the strong count. Otherwise * return 0 */ [[nodiscard]] std::size_t useCount() const; /** * Return true if there is a non-zero strong count. */ [[nodiscard]] bool expired() const; /** * If this is a strong pointer, return the strong pointer. Otherwise * attempt to lock the weak pointer. */ [[nodiscard]] SharedIntrusive lock() const; /** * Return true is this represents a strong pointer. */ [[nodiscard]] bool isStrong() const; /** * Return true is this represents a weak pointer. */ [[nodiscard]] bool isWeak() const; /** * If this is a weak pointer, attempt to convert it to a strong * pointer. * * @return true if successfully converted to a strong pointer (or was * already a strong pointer). Otherwise false. */ bool convertToStrong(); /** * If this is a strong pointer, attempt to convert it to a weak * pointer. * * @return false if the pointer is null. Otherwise return true. */ bool convertToWeak(); private: // Tagged pointer. Low bit determines if this is a strong or a weak // pointer. The low bit must be masked to zero when converting back to a // pointer. If the low bit is '1', this is a weak pointer. std::uintptr_t tp_{0}; static constexpr std::uintptr_t kTagMask = 1; static constexpr std::uintptr_t kPtrMask = ~kTagMask; private: /** * Return the raw pointer held by this object. */ [[nodiscard]] T* unsafeGetRawPtr() const; enum class RefStrength { Strong, Weak }; /** * Set the raw pointer and tag bit directly. */ void unsafeSetRawPtr(T* p, RefStrength rs); /** * Set the raw pointer and tag bit to all zeros (strong null pointer). */ void unsafeSetRawPtr(std::nullptr_t); /** * Decrement the appropriate ref count, and run the appropriate release * action. Note: this does _not_ set the raw pointer to null. */ void unsafeReleaseNoStore(); }; //------------------------------------------------------------------------------ /** * Create a shared intrusive pointer. * * Note: unlike std::shared_ptr, where there is an advantage of allocating * the pointer and control block together, there is no benefit for intrusive * pointers. */ template SharedIntrusive makeSharedIntrusive(Args&&... args) { auto p = new TT(std::forward(args)...); static_assert( noexcept(SharedIntrusive( std::declval(), std::declval())), "SharedIntrusive constructor should not throw or this can leak " "memory"); return SharedIntrusive(p, SharedIntrusiveAdoptNoIncrementTag{}); } //------------------------------------------------------------------------------ namespace intr_ptr { template using SharedPtr = SharedIntrusive; template using WeakPtr = WeakIntrusive; template using SharedWeakUnionPtr = SharedWeakUnion; template SharedPtr makeShared(A&&... args) { return makeSharedIntrusive(std::forward(args)...); } template SharedPtr staticPointerCast(TT const& v) { return SharedPtr(StaticCastTagSharedIntrusive{}, v); } template SharedPtr dynamicPointerCast(TT const& v) { return SharedPtr(DynamicCastTagSharedIntrusive{}, v); } } // namespace intr_ptr } // namespace xrpl