|
| 1 | +// |
| 2 | +// WeakRef.hh |
| 3 | +// |
| 4 | +// Copyright 2026-Present Couchbase, Inc. |
| 5 | +// |
| 6 | +// Use of this software is governed by the Business Source License included |
| 7 | +// in the file licenses/BSL-Couchbase.txt. As of the Change Date specified |
| 8 | +// in that file, in accordance with the Business Source License, use of this |
| 9 | +// software will be governed by the Apache License, Version 2.0, included in |
| 10 | +// the file licenses/APL2.txt. |
| 11 | +// |
| 12 | + |
| 13 | +#pragma once |
| 14 | +#include "RefCounted.hh" |
| 15 | + |
| 16 | +namespace fleece { |
| 17 | + |
| 18 | + /** A smart pointer very much like \ref Retained except that it holds a _weak_ reference. |
| 19 | + * The existence of the weak reference does not keep the referred-to object alive on its own; |
| 20 | + * once no strong references exist, the object is freed and any weak references cleared. |
| 21 | + * |
| 22 | + * `WeakRetained` is useful for breaking reference cycles that could otherwise cause leaks. |
| 23 | + * If one object in the cycle holds a weak reference to the other, the objects will be freed |
| 24 | + * properly once there are no external references to them. |
| 25 | + * |
| 26 | + * Because the pointer is cleared when the last strong reference goes away, which could happen |
| 27 | + * on another thread, you have to dereference a `WeakRetained` carefully. You cannot get a |
| 28 | + * plain pointer, because the object could be freed at any moment. You must use \ref tryGet, |
| 29 | + * which returns a strong reference, i.e. a `Retained` object. If the object has already been |
| 30 | + * freed, \ref tryGet returns nullptr (wrapped in a `Retained`), so you have to test for that |
| 31 | + * before dereferencing it. */ |
| 32 | + template <class T, Nullability N = MaybeNull> |
| 33 | + class WeakRetained { |
| 34 | + public: |
| 35 | + #if __has_feature(nullability) |
| 36 | + template <typename X, Nullability> struct nullable_if; |
| 37 | + template <typename X> struct nullable_if<X,MaybeNull> {using ptr = X* _Nullable;}; |
| 38 | + template <typename X> struct nullable_if<X,NonNull> {using ptr = X* _Nonnull;}; |
| 39 | + #else |
| 40 | + template <typename X, Nullability> struct nullable_if {using ptr = X*;}; |
| 41 | + #endif |
| 42 | + |
| 43 | + using T_ptr = typename nullable_if<T,N>::ptr; // This is `T*` with appropriate nullability |
| 44 | + |
| 45 | + WeakRetained() noexcept requires (N==MaybeNull) :_ref(nullptr) { } |
| 46 | + WeakRetained(std::nullptr_t) noexcept requires (N==MaybeNull) :WeakRetained() { } // optimization |
| 47 | + |
| 48 | + WeakRetained(T* _Nullable t) noexcept :_ref(_weakRetain(t)) { } |
| 49 | + WeakRetained(T* _Nonnull t) noexcept requires(N==MaybeNull) :_ref(_weakRetain(t)) { } |
| 50 | + |
| 51 | + WeakRetained(const WeakRetained &r) noexcept :_ref(_weakRetain(r._ref)) { } |
| 52 | + WeakRetained(WeakRetained &&r) noexcept :_ref(std::move(r).detach()) { } |
| 53 | + |
| 54 | + template <typename U, Nullability UN> requires (std::derived_from<U,T> && N >= UN) |
| 55 | + WeakRetained(const WeakRetained<U,UN> &r) noexcept :_ref(_weakRetain(r._ref)) { } |
| 56 | + template <typename U, Nullability UN> requires (std::derived_from<U,T> && N >= UN) |
| 57 | + WeakRetained(WeakRetained<U,UN> &&r) noexcept :_ref(std::move(r).detach()) { } |
| 58 | + |
| 59 | + ~WeakRetained() noexcept {if (_ref) _ref->_weakRelease();} |
| 60 | + |
| 61 | + WeakRetained& operator=(T_ptr t) & noexcept { |
| 62 | + _weakRetain(t); |
| 63 | + std::swap(_ref, t); |
| 64 | + if (t) t->_weakRelease(); |
| 65 | + return *this; |
| 66 | + } |
| 67 | + |
| 68 | + WeakRetained& operator=(std::nullptr_t) & noexcept requires(N==MaybeNull) { // optimized assignment |
| 69 | + auto oldRef = _ref; |
| 70 | + _ref = nullptr; |
| 71 | + if (oldRef) oldRef->_weakRelease(); |
| 72 | + return *this; |
| 73 | + } |
| 74 | + |
| 75 | + WeakRetained& operator=(const WeakRetained &r) & noexcept {*this = r._ref; return *this;} |
| 76 | + |
| 77 | + template <typename U, Nullability UN> requires(std::derived_from<U,T> && N >= UN) |
| 78 | + WeakRetained& operator=(const WeakRetained<U,UN> &r) & noexcept {*this = r._ref; return *this;} |
| 79 | + |
| 80 | + WeakRetained& operator= (WeakRetained &&r) & noexcept { |
| 81 | + std::swap(_ref, r._ref); // old _ref will be released by r's destructor |
| 82 | + return *this; |
| 83 | + } |
| 84 | + |
| 85 | + template <typename U, Nullability UN> requires(std::derived_from<U,T> && N >= UN) |
| 86 | + WeakRetained& operator= (WeakRetained<U,UN> &&r) & noexcept { |
| 87 | + if ((void*)&r != this) { |
| 88 | + auto oldRef = _ref; |
| 89 | + _ref = std::move(r).detach(); |
| 90 | + _weakRelease(oldRef); |
| 91 | + } |
| 92 | + return *this; |
| 93 | + } |
| 94 | + |
| 95 | + /// Returns true if I hold a non-null pointer. |
| 96 | + /// Does _not_ check if the pointed-to object still exists. |
| 97 | + /// @warning Do not use this to preflight \ref asRetained. |
| 98 | + explicit operator bool () const FLPURE {return N==NonNull || (_ref != nullptr);} |
| 99 | + |
| 100 | + /// Converts any WeakRetained to non-nullable form (WeakRef), or throws if its value is nullptr. |
| 101 | + WeakRetained<T,NonNull> asWeakRef() const & noexcept(!N) {return WeakRetained<T,NonNull>(_ref);} |
| 102 | + WeakRetained<T,NonNull> asWeakRef() && noexcept(!N) { |
| 103 | + WeakRetained<T,NonNull> result(_ref, false); |
| 104 | + _ref = nullptr; |
| 105 | + return result; |
| 106 | + } |
| 107 | + |
| 108 | + /// True if the object no longer exists. |
| 109 | + bool invalidated() const FLPURE {return !_ref || _ref->refCount() == 0;} |
| 110 | + |
| 111 | + /// If this holds a non-null pointer, and the object pointed to still exists, |
| 112 | + /// returns a `Retained` instance holding a new strong reference to it. |
| 113 | + /// Otherwise returns an empty (nullptr) `Retained`. |
| 114 | + /// @warning You **must** check the `Retained` for null before dereferencing it! |
| 115 | + Retained<T> tryGet() const { |
| 116 | + if (_ref->_weakToStrong()) |
| 117 | + return Retained<T>::adopt(_ref); |
| 118 | + else |
| 119 | + return nullptr; |
| 120 | + } |
| 121 | + |
| 122 | + /// An alternative to \ref tryGet. If the object pointed to still exists, |
| 123 | + /// calls the function `fn` with a pointer to it, then returns true. |
| 124 | + /// Otherwise just returns false. |
| 125 | + template <std::invocable<T*> FN> |
| 126 | + [[nodiscard]] bool use(FN&& fn) requires(std::is_void_v<std::invoke_result_t<FN,T*>>) { |
| 127 | + if (auto ref = tryGet()) { |
| 128 | + fn(ref.get()); |
| 129 | + return true; |
| 130 | + } else { |
| 131 | + return false; |
| 132 | + } |
| 133 | + } |
| 134 | + |
| 135 | + /// An alternative to \ref tryGet. If the object pointed to still exists, |
| 136 | + /// calls the function `fn` with a pointer to it, returning whatever it returned. |
| 137 | + /// Otherwise calls `elsefn` with no arguments, returning whatever it returned. |
| 138 | + template <std::invocable<T*> FN, std::invocable<> ELSEFN> |
| 139 | + auto use(FN&& fn, ELSEFN&& elsefn) { |
| 140 | + if (auto ref = tryGet()) { |
| 141 | + return fn(ref.get()); |
| 142 | + } else { |
| 143 | + return elsefn(); |
| 144 | + } |
| 145 | + } |
| 146 | + |
| 147 | + private: |
| 148 | + template <class U, Nullability UN> friend class WeakRetained; |
| 149 | + |
| 150 | + WeakRetained(T_ptr t, bool) noexcept(N==MaybeNull) // private no-retain ctor |
| 151 | + :_ref(t) { |
| 152 | + if constexpr (N == NonNull) { |
| 153 | + if (t == nullptr) [[unlikely]] |
| 154 | + _failNullRef(); |
| 155 | + } |
| 156 | + } |
| 157 | + |
| 158 | + static T_ptr _weakRetain(T_ptr t) noexcept { |
| 159 | + if constexpr (N == NonNull) { |
| 160 | + t->_weakRetain(); // this is faster, and it detects illegal null (by signal) |
| 161 | + } else { |
| 162 | + if (t) t->_weakRetain(); |
| 163 | + } |
| 164 | + return t; |
| 165 | + } |
| 166 | + |
| 167 | + static void _weakRelease(T_ptr t) noexcept { |
| 168 | + if constexpr (N == NonNull) { |
| 169 | + t->_weakRelease(); // this is faster, and it detects illegal null (by signal) |
| 170 | + } else { |
| 171 | + if (t) t->_weakRelease(); |
| 172 | + } |
| 173 | + } |
| 174 | + |
| 175 | + // _ref has to be declared nullable, even when N==NonNull, because a move assignment |
| 176 | + // sets the moved-from _ref to nullptr. The WeakRetained may not used any more in this state, |
| 177 | + // but it will be destructed, which is why the destructor also checks for nullptr. |
| 178 | + T* FL_NULLABLE _ref; |
| 179 | + }; |
| 180 | + |
| 181 | + template <class T> WeakRetained(T* FL_NULLABLE) -> WeakRetained<T>; // deduction guide |
| 182 | + |
| 183 | + /// WeakRef<T> is an alias for a non-nullable WeakRetained<T>. |
| 184 | + template <class T> using WeakRef = WeakRetained<T, NonNull>; |
| 185 | + |
| 186 | + /// NullableWeakRef<T> is an alias for a (default) nullable WeakRetained<T>. |
| 187 | + template <class T> using NullableWeakRef = WeakRetained<T, MaybeNull>; |
| 188 | + |
| 189 | + /// WeakRetainedConst is an alias for a WeakRetained that holds a const pointer. |
| 190 | + template <class T> using WeakRetainedConst = WeakRetained<const T>; |
| 191 | + |
| 192 | +} |
0 commit comments