#ifndef XRPL_BASICS_INTRUSIVEPOINTER_H_INCLUDED #define XRPL_BASICS_INTRUSIVEPOINTER_H_INCLUDED #include #include #include #include namespace ripple { //------------------------------------------------------------------------------ /** 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_poiner 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 rippled, 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); 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); /** 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 */ T* get() const; /** Return the strong count */ std::size_t use_count() const; template friend SharedIntrusive make_SharedIntrusive(Args&&... args); template friend class SharedIntrusive; template friend class SharedWeakUnion; template friend class WeakIntrusive; private: /** Return the raw pointer held by this object. */ 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. */ 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); SharedWeakUnion& operator=(SharedWeakUnion const& rhs); template requires std::convertible_to SharedWeakUnion& operator=(SharedIntrusive const& rhs); template requires std::convertible_to SharedWeakUnion& operator=(SharedIntrusive&& rhs); ~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) */ 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. */ T* get() const; /** If this is a strong pointer, return the strong count. Otherwise * return 0 */ std::size_t use_count() const; /** Return true if there is a non-zero strong count. */ bool expired() const; /** If this is a strong pointer, return the strong pointer. Otherwise attempt to lock the weak pointer. */ SharedIntrusive lock() const; /** Return true is this represents a strong pointer. */ bool isStrong() const; /** Return true is this represents a weak pointer. */ 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 tagMask = 1; static constexpr std::uintptr_t ptrMask = ~tagMask; private: /** Return the raw pointer held by this object. */ 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 make_SharedIntrusive(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 make_shared(A&&... args) { return make_SharedIntrusive(std::forward(args)...); } template SharedPtr static_pointer_cast(TT const& v) { return SharedPtr(StaticCastTagSharedIntrusive{}, v); } template SharedPtr dynamic_pointer_cast(TT const& v) { return SharedPtr(DynamicCastTagSharedIntrusive{}, v); } } // namespace intr_ptr } // namespace ripple #endif