Skip to content

Commit f5102a3

Browse files
committed
1 parent 7a6dd0e commit f5102a3

File tree

4 files changed

+420
-81
lines changed

4 files changed

+420
-81
lines changed

API/fleece/RefCounted.hh

Lines changed: 20 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -24,16 +24,23 @@ namespace fleece {
2424

2525
enum Nullability {NonNull, MaybeNull};
2626

27-
/** Simple thread-safe ref-counting implementation.
27+
/** Thread-safe ref-counting implementation.
2828
`RefCounted` objects should be managed by \ref Retained smart-pointers:
2929
`Retained<Foo> foo = new Foo(...)` or `auto foo = make_retained<Foo>(...)`.
3030
\note The ref-count starts at 0, so you must call retain() on an instance, or assign it
3131
to a Retained, right after constructing it. */
3232
class RefCounted {
3333
public:
3434
RefCounted() =default;
35-
36-
int refCount() const FLPURE {return _refCount;}
35+
36+
/// The number of (strong) references to this object.
37+
int refCount() const FLPURE;
38+
39+
/// Returns the strong and the weak reference count.
40+
std::pair<int,int> refCounts() const FLPURE;
41+
42+
/// The global number of objects with weak references but no strong references.
43+
static size_t zombieCount();
3744

3845
protected:
3946
RefCounted(const RefCounted &) { } // must not copy the refCount!
@@ -44,28 +51,25 @@ namespace fleece {
4451

4552
private:
4653
template <typename T, Nullability N> friend class Retained;
54+
template <typename T, Nullability N> friend class WeakRetained;
4755
template <typename T> friend T* FL_NULLABLE retain(T* FL_NULLABLE) noexcept;
4856
friend void release(const RefCounted* FL_NULLABLE) noexcept;
4957
friend void assignRef(RefCounted* FL_NULLABLE &dst, RefCounted* FL_NULLABLE src) noexcept;
5058

5159
#if DEBUG
52-
void _retain() const noexcept {_careful_retain();}
53-
void _release() const noexcept {_careful_release();}
60+
void _retain() const noexcept;
61+
static constexpr uint64_t kInitialRefCount = 0x66666666;
5462
#else
55-
ALWAYS_INLINE void _retain() const noexcept { ++_refCount; }
56-
void _release() const noexcept;
63+
ALWAYS_INLINE void _retain() const noexcept { ++_bothRefCounts; }
64+
static constexpr uint64_t kInitialRefCount = 1;
5765
#endif
66+
void _release() const noexcept;
5867

59-
static constexpr int32_t kCarefulInitialRefCount = -6666666;
60-
void _careful_retain() const noexcept;
61-
void _careful_release() const noexcept;
68+
void _weakRetain() const noexcept;
69+
void _weakRelease() const noexcept;
70+
bool _weakToStrong() const noexcept;
6271

63-
mutable std::atomic<int32_t> _refCount
64-
#if DEBUG
65-
{kCarefulInitialRefCount};
66-
#else
67-
{0};
68-
#endif
72+
mutable std::atomic<uint64_t> _bothRefCounts {kInitialRefCount};
6973
};
7074

7175
template <class T> concept RefCountedType = std::derived_from<T, RefCounted>;

API/fleece/WeakRef.hh

Lines changed: 192 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,192 @@
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

Comments
 (0)