Async++ unknown
Async (co_await/co_return) code for C++
Loading...
Searching...
No Matches
ref.h
1#pragma once
2#include <atomic>
3#include <cassert>
4#include <compare>
5#include <cstddef>
6#include <cstdint>
7#include <functional>
8#include <stdexcept>
9#include <type_traits>
10#include <utility>
11
12namespace asyncpp {
16 template<typename T>
17 concept RefCount = requires() {
18 { T{std::declval<size_t>()} };
19 { std::declval<T&>().fetch_increment() } -> std::convertible_to<size_t>;
20 { std::declval<T&>().fetch_decrement() } -> std::convertible_to<size_t>;
21 { std::declval<const T&>().count() } -> std::convertible_to<size_t>;
22 };
23
28 std::atomic<size_t> m_count;
29 explicit constexpr thread_safe_refcount(size_t init_val = 0) noexcept : m_count{init_val} {}
30 thread_safe_refcount(const thread_safe_refcount& other) = delete;
31 thread_safe_refcount& operator=(const thread_safe_refcount& other) = delete;
32
33 size_t fetch_increment() noexcept { return m_count.fetch_add(1, std::memory_order::acquire); }
34 [[nodiscard]] size_t fetch_decrement() noexcept { return m_count.fetch_sub(1, std::memory_order::release); }
35 [[nodiscard]] size_t count() const noexcept { return m_count.load(std::memory_order::relaxed); }
36 };
41 size_t m_count;
42 explicit constexpr thread_unsafe_refcount(size_t init_val = 0) noexcept : m_count{init_val} {}
44 thread_unsafe_refcount& operator=(const thread_unsafe_refcount& other) = delete;
45
46 size_t fetch_increment() noexcept { return m_count++; }
47 [[nodiscard]] size_t fetch_decrement() noexcept { return m_count--; }
48 [[nodiscard]] size_t count() const noexcept { return m_count; }
49 };
50
51 static_assert(RefCount<thread_safe_refcount>, "[INTERNAL] thread_safe_refcount does not satisfy RefCount");
52 static_assert(RefCount<thread_unsafe_refcount>, "[INTERNAL] thread_unsafe_refcount does not satisfy RefCount");
53
54 template<typename T, RefCount TCounter = thread_safe_refcount>
56
62 template<typename T, RefCount TCounter>
64 public:
65 intrusive_refcount() = default;
66
67 protected:
68 ~intrusive_refcount() noexcept = default;
73 size_t use_count() const noexcept { return m_refcount.count(); }
74
78 void add_ref() const noexcept {
79 static_assert(std::is_base_of_v<intrusive_refcount<T, TCounter>, T>,
80 "T needs to inherit intrusive_refcount<T>");
81 m_refcount.fetch_increment();
82 }
83
89 void remove_ref() const noexcept {
90 static_assert(std::is_nothrow_destructible_v<T>, "Destructor needs to be noexcept!");
91 static_assert(std::is_base_of_v<intrusive_refcount<T, TCounter>, T>,
92 "T needs to inherit intrusive_refcount<T>");
93 auto cnt = m_refcount.fetch_decrement();
94 if (cnt == 1) delete static_cast<const T*>(this);
95 }
96
97 private:
98 mutable TCounter m_refcount{0};
99
100 friend inline void refcounted_add_ref(const intrusive_refcount<T, TCounter>* ptr) noexcept {
101 if (ptr) ptr->add_ref();
102 }
103 friend inline void refcounted_remove_ref(const intrusive_refcount<T, TCounter>* ptr) noexcept {
104 if (ptr) ptr->remove_ref();
105 }
106 };
107
108 inline constexpr struct {
109 } adopt_ref{};
110
115 template<typename T>
116 concept RefCountable = requires(T* obj) {
117 { refcounted_add_ref(obj) };
118 { refcounted_remove_ref(obj) };
119 };
120
125 template<typename T>
126 class ref {
127 T* m_ptr;
128
129 public:
133 constexpr ref() noexcept : m_ptr(nullptr) { static_assert(RefCountable<T>, "T needs to be refcountable"); }
139 ref(T* ptr, decltype(adopt_ref)) noexcept : m_ptr{ptr} {
140 static_assert(RefCountable<T>, "T needs to be refcountable");
141 }
146 // NOLINTNEXTLINE(google-explicit-constructor)
147 ref(T* ptr) noexcept(noexcept(refcounted_add_ref(std::declval<T*>()))) : m_ptr{ptr} {
148 static_assert(RefCountable<T>, "T needs to be refcountable");
149 if (m_ptr) refcounted_add_ref(m_ptr);
150 }
158 ref(const ref& other) noexcept(noexcept(refcounted_add_ref(std::declval<T*>()))) : m_ptr{other.m_ptr} {
159 if (m_ptr) refcounted_add_ref(m_ptr);
160 }
162 constexpr ref(ref&& other) noexcept : m_ptr{std::exchange(other.m_ptr, nullptr)} {}
164 ref& operator=(const ref& other) noexcept(noexcept(refcounted_add_ref(std::declval<T*>())) &&
165 noexcept(refcounted_remove_ref(std::declval<T*>()))) {
166 if (&other != this) reset(other.m_ptr);
167 return *this;
168 }
170 //NOLINTNEXTLINE(hicpp-noexcept-move,performance-noexcept-move-constructor)
171 ref& operator=(ref&& other) noexcept(noexcept(refcounted_remove_ref(std::declval<T*>()))) {
172 if (m_ptr) refcounted_remove_ref(m_ptr);
173 m_ptr = std::exchange(other.m_ptr, nullptr);
174 return *this;
175 }
181 void reset(T* ptr) noexcept(noexcept(refcounted_add_ref(std::declval<T*>())) &&
182 noexcept(refcounted_remove_ref(std::declval<T*>()))) {
183 if (m_ptr) refcounted_remove_ref(m_ptr);
184 m_ptr = ptr;
185 if (m_ptr) refcounted_add_ref(m_ptr);
186 }
192 void reset(T* ptr, decltype(adopt_ref)) noexcept(noexcept(refcounted_remove_ref(std::declval<T*>()))) {
193 if (m_ptr) refcounted_remove_ref(m_ptr);
194 m_ptr = ptr;
195 }
199 void reset() noexcept(noexcept(refcounted_remove_ref(std::declval<T*>()))) { reset(nullptr, adopt_ref); }
201 //NOLINTNEXTLINE(performance-noexcept-destructor)
202 ~ref() noexcept(noexcept(refcounted_remove_ref(std::declval<T*>()))) { reset(); }
204 constexpr T* operator->() const noexcept { return m_ptr; }
206 constexpr T& operator*() const noexcept { return *m_ptr; }
208 constexpr T* get() const noexcept { return m_ptr; }
210 constexpr T* release() noexcept {
211 auto ptr = m_ptr;
212 m_ptr = nullptr;
213 return ptr;
214 }
216 explicit constexpr operator bool() const noexcept { return m_ptr != nullptr; }
218 constexpr bool operator!() const noexcept { return m_ptr == nullptr; }
219 };
220
221 template<RefCountable T, typename... Args>
222 ref<T> make_ref(Args&&... args) {
223 return ref<T>(new T(std::forward<Args>(args)...));
224 }
225
237 template<RefCountable T>
239 friend struct std::hash<asyncpp::atomic_ref<T>>;
240 template<RefCountable T2>
241 friend constexpr auto operator<=>(const atomic_ref<T2>& lhs, const atomic_ref<T2>& rhs) noexcept {
242 return (lhs.m_ptr.load(std::memory_order::relaxed) & ~atomic_ref<T2>::lock_mask) <=>
243 (rhs.m_ptr.load(std::memory_order::relaxed) & ~atomic_ref<T2>::lock_mask);
244 }
245 template<RefCountable T2>
246 friend constexpr auto operator<=>(const atomic_ref<T2>& lhs, const T2* rhs) noexcept {
247 return (lhs.m_ptr.load(std::memory_order::relaxed) & ~atomic_ref<T>::lock_mask) <=>
248 reinterpret_cast<uintptr_t>(rhs);
249 }
250 template<RefCountable T2>
251 friend constexpr auto operator<=>(const T2* lhs, const atomic_ref<T2>& rhs) noexcept {
252 return reinterpret_cast<uintptr_t>(lhs) <=>
253 (rhs.m_ptr.load(std::memory_order::relaxed) & ~atomic_ref<T>::lock_mask);
254 }
255 mutable std::atomic<uintptr_t> m_ptr;
256
257 static constexpr uintptr_t lock_mask = uintptr_t{1} << (sizeof(uintptr_t) * 8 - 1);
258
263 inline static void cpu_pause() noexcept {
264#if defined(__i386) || defined(_M_IX86) || defined(_X86_) || defined(__amd64) || defined(_M_AMD64)
265#ifdef _MSC_VER
266 _mm_pause();
267#else
268 __builtin_ia32_pause();
269#endif
270#elif defined(__arm__) || defined(_ARM) || defined(_M_ARM) || defined(__arm)
271#ifdef _MSC_VER
272 __yield();
273#else
274 asm volatile("yield");
275#endif
276#elif defined(__riscv)
277 asm volatile("pause");
278#endif
279 }
280
284 uintptr_t lock() const noexcept {
285 // Lock the current pointer value
286 auto val = m_ptr.fetch_or(lock_mask, std::memory_order_acquire);
287 while ((val & lock_mask) == lock_mask) {
288 cpu_pause();
289 val = m_ptr.fetch_or(lock_mask, std::memory_order_acquire);
290 }
291 return val;
292 }
293
297 void unlock_with(uintptr_t val) const noexcept {
298 assert((val & lock_mask) == 0);
299 [[maybe_unused]] auto res = m_ptr.exchange(val, std::memory_order_release);
300 assert((res & lock_mask) == lock_mask);
301 }
302
303 public:
307 constexpr atomic_ref() noexcept : m_ptr(0) {}
314 // NOLINTNEXTLINE(google-explicit-constructor)
316 if ((reinterpret_cast<uintptr_t>(ptr.get()) & lock_mask) != 0) throw std::logic_error("invalid pointer");
317 m_ptr = reinterpret_cast<uintptr_t>(ptr.release());
318 }
320 atomic_ref& operator=(const ref<T>& other) {
321 exchange(other);
322 return *this;
323 }
326 exchange(ref<T>(other.release(), adopt_ref));
327 return *this;
328 }
331 operator=(const atomic_ref<T>& other) noexcept(noexcept(refcounted_add_ref(std::declval<T*>())) &&
332 noexcept(refcounted_remove_ref(std::declval<T*>()))) {
333 if (&other != this) exchange(other.load());
334 return *this;
335 }
337 //NOLINTNEXTLINE(hicpp-noexcept-move,performance-noexcept-move-constructor)
338 atomic_ref& operator=(atomic_ref<T>&& other) noexcept(noexcept(refcounted_remove_ref(std::declval<T*>()))) {
339 exchange(ref<T>(other.release(), adopt_ref));
340 return *this;
341 }
343 //NOLINTNEXTLINE(performance-noexcept-destructor)
344 ~atomic_ref() noexcept(noexcept(refcounted_remove_ref(std::declval<T*>()))) { exchange(ref<T>()); }
346 ref<T> load() const noexcept(noexcept(refcounted_add_ref(std::declval<T*>()))) {
347 // Early out if nullptr
348 if ((m_ptr.load(std::memory_order_relaxed) & ~lock_mask) == 0) return 0;
349
350 // Lock the pointer to prevent concurrent modification
351 auto val = lock();
352 // Get original pointer
353 auto ptr = reinterpret_cast<T*>(val);
354 // Add reference
355 if (ptr) refcounted_add_ref(ptr);
356 // Unlock again
357 unlock_with(val);
358 return ref<T>(ptr, adopt_ref);
359 }
365 void store(ref<T> hdl) { exchange(std::move(hdl)); }
370 void store(const atomic_ref<T>& hdl) { exchange(hdl.load()); }
378 auto ptr = hdl.release();
379 if ((reinterpret_cast<uintptr_t>(ptr) & lock_mask) != 0) throw std::logic_error("invalid pointer");
380
381 // Lock the current pointer value
382 auto val = lock();
383 // Unlock again with new value
384 unlock_with(reinterpret_cast<uintptr_t>(ptr));
385 // Return the old pointer without incrementing the reference count
386 return ref<T>(reinterpret_cast<T*>(val), adopt_ref);
387 }
392 ref<T> exchange(const atomic_ref<T>& hdl) { return exchange(hdl.load()); }
394 T* release() noexcept { return exchange(ref<T>()).release(); }
396 void reset() noexcept(noexcept(refcounted_remove_ref(std::declval<T*>()))) { exchange(ref<T>()); }
398 //NOLINTNEXTLINE(google-explicit-constructor)
399 operator bool() const noexcept { return (m_ptr.load(std::memory_order_relaxed) & ~lock_mask) != 0; }
401 bool operator!() const noexcept { return (m_ptr.load(std::memory_order_relaxed) & ~lock_mask) == 0; }
403 ref<T> operator->() const noexcept(noexcept(refcounted_add_ref(std::declval<T*>()))) { return load(); }
404 };
405
406 template<typename T>
407 inline constexpr auto operator<=>(const ref<T>& lhs, const ref<T>& rhs) noexcept {
408 return lhs.get() <=> rhs.get();
409 }
410 template<typename T>
411 inline constexpr auto operator<=>(const ref<T>& lhs, const T* rhs) noexcept {
412 return lhs.get() <=> rhs;
413 }
414 template<typename T>
415 inline constexpr auto operator<=>(const T* lhs, const ref<T>& rhs) noexcept {
416 return lhs <=> rhs.get();
417 }
418
419 template<typename T>
420 inline constexpr auto operator==(const ref<T>& lhs, const ref<T>& rhs) noexcept {
421 return (lhs <=> rhs) == std::strong_ordering::equal;
422 }
423 template<typename T>
424 inline constexpr auto operator==(const ref<T>& lhs, const T* rhs) noexcept {
425 return (lhs <=> rhs) == std::strong_ordering::equal;
426 }
427 template<typename T>
428 inline constexpr auto operator==(const T* lhs, const ref<T>& rhs) noexcept {
429 return (lhs <=> rhs) == std::strong_ordering::equal;
430 }
431 template<typename T>
432 inline constexpr auto operator!=(const ref<T>& lhs, const ref<T>& rhs) noexcept {
433 return (lhs <=> rhs) != std::strong_ordering::equal;
434 }
435 template<typename T>
436 inline constexpr auto operator!=(const ref<T>& lhs, const T* rhs) noexcept {
437 return (lhs <=> rhs) != std::strong_ordering::equal;
438 }
439 template<typename T>
440 inline constexpr auto operator!=(const T* lhs, const ref<T>& rhs) noexcept {
441 return (lhs <=> rhs) != std::strong_ordering::equal;
442 }
443
444 template<typename T>
445 inline constexpr auto operator==(const atomic_ref<T>& lhs, const atomic_ref<T>& rhs) noexcept {
446 return (lhs <=> rhs) == std::strong_ordering::equal;
447 }
448 template<typename T>
449 inline constexpr auto operator==(const atomic_ref<T>& lhs, const T* rhs) noexcept {
450 return (lhs <=> rhs) == std::strong_ordering::equal;
451 }
452 template<typename T>
453 inline constexpr auto operator==(const T* lhs, const atomic_ref<T>& rhs) noexcept {
454 return (lhs <=> rhs) == std::strong_ordering::equal;
455 }
456 template<typename T>
457 inline constexpr auto operator!=(const atomic_ref<T>& lhs, const atomic_ref<T>& rhs) noexcept {
458 return (lhs <=> rhs) != std::strong_ordering::equal;
459 }
460 template<typename T>
461 inline constexpr auto operator!=(const atomic_ref<T>& lhs, const T* rhs) noexcept {
462 return (lhs <=> rhs) != std::strong_ordering::equal;
463 }
464 template<typename T>
465 inline constexpr auto operator!=(const T* lhs, const atomic_ref<T>& rhs) noexcept {
466 return (lhs <=> rhs) != std::strong_ordering::equal;
467 }
468
469 template<typename T, typename U>
470 ref<T> static_ref_cast(const ref<U>& rhs) noexcept {
471 return ref<T>(static_cast<T*>(rhs.get()));
472 }
473 template<typename T, typename U>
474 ref<T> static_ref_cast(ref<U>&& rhs) noexcept {
475 return ref<T>(static_cast<T*>(rhs.release()), adopt_ref);
476 }
477 template<typename T, typename U>
478 ref<T> const_ref_cast(const ref<U>& rhs) noexcept {
479 return ref<T>(const_cast<T*>(rhs.get()));
480 }
481 template<typename T, typename U>
482 ref<T> const_ref_cast(ref<U>&& rhs) noexcept {
483 return ref<T>(const_cast<T*>(rhs.release()), adopt_ref);
484 }
485 template<typename T, typename U>
486 ref<T> dynamic_ref_cast(const ref<U>& rhs) noexcept {
487 return ref<T>(dynamic_cast<T*>(rhs.get()));
488 }
489 template<typename T, typename U>
490 ref<T> dynamic_ref_cast(ref<U>&& rhs) noexcept {
491 auto ptr = dynamic_cast<T*>(rhs.get());
492 if (!ptr) return ref<T>();
493 // We already hold the correct pointer using get(),
494 // so only clear it without removing the ref
495 rhs.release();
496 return ref<T>(ptr, adopt_ref);
497 }
498
499} // namespace asyncpp
500
501template<typename T>
502//NOLINTNEXTLINE(cert-dcl58-cpp)
503struct std::hash<asyncpp::ref<T>> {
504 constexpr size_t operator()(const asyncpp::ref<T>& rhs) const noexcept { return std::hash<void*>{}(rhs.get()); }
505};
506
507template<typename T>
508//NOLINTNEXTLINE(cert-dcl58-cpp)
509struct std::hash<asyncpp::atomic_ref<T>> {
510 constexpr size_t operator()(const asyncpp::atomic_ref<T>& rhs) const noexcept {
511 auto ptr = rhs.m_ptr.load(std::memory_order::relaxed) & ~asyncpp::atomic_ref<T>::lock_mask;
512 return std::hash<uintptr_t>{}(ptr);
513 }
514};
Thread safe reference handle.
Definition ref.h:238
ref< T > exchange(ref< T > hdl)
Reset the handle with a new value.
Definition ref.h:377
ref< T > exchange(const atomic_ref< T > &hdl)
Reset the handle with a new value.
Definition ref.h:392
~atomic_ref() noexcept(noexcept(refcounted_remove_ref(std::declval< T * >())))
Destructor.
Definition ref.h:344
atomic_ref(ref< T > ptr)
Construct a new atomic_ref object.
Definition ref.h:315
void reset() noexcept(noexcept(refcounted_remove_ref(std::declval< T * >())))
Reset the pointer to nullptr.
Definition ref.h:396
atomic_ref & operator=(atomic_ref< T > &&other) noexcept(noexcept(refcounted_remove_ref(std::declval< T * >())))
Move assignment operator.
Definition ref.h:338
void store(const atomic_ref< T > &hdl)
Store a new value and destroy the old one.
Definition ref.h:370
atomic_ref & operator=(const atomic_ref< T > &other) noexcept(noexcept(refcounted_add_ref(std::declval< T * >())) &&noexcept(refcounted_remove_ref(std::declval< T * >())))
Assignment operator.
Definition ref.h:331
ref< T > load() const noexcept(noexcept(refcounted_add_ref(std::declval< T * >())))
Get the contained value.
Definition ref.h:346
bool operator!() const noexcept
Check if the handle contains no pointer.
Definition ref.h:401
atomic_ref & operator=(ref< T > &&other)
Move assignment operator.
Definition ref.h:325
atomic_ref & operator=(const ref< T > &other)
Assignment operator.
Definition ref.h:320
T * release() noexcept
Release the contained pointer.
Definition ref.h:394
constexpr atomic_ref() noexcept
Construct an empty atomic_ref.
Definition ref.h:307
void store(ref< T > hdl)
Store a new value and destroy the old one.
Definition ref.h:365
ref< T > operator->() const noexcept(noexcept(refcounted_add_ref(std::declval< T * >())))
Dereference this handle.
Definition ref.h:403
Intrusive refcounting base class.
Definition ref.h:63
void remove_ref() const noexcept
Decrement the reference count and delete the object if the last reference is removed.
Definition ref.h:89
size_t use_count() const noexcept
Get the current use_count of this object.
Definition ref.h:73
void add_ref() const noexcept
Increment the reference count.
Definition ref.h:78
Reference count handle.
Definition ref.h:126
constexpr T * release() noexcept
Release the contained pointer.
Definition ref.h:210
void reset() noexcept(noexcept(refcounted_remove_ref(std::declval< T * >())))
Reset the handle to nullptr.
Definition ref.h:199
ref & operator=(const ref &other) noexcept(noexcept(refcounted_add_ref(std::declval< T * >())) &&noexcept(refcounted_remove_ref(std::declval< T * >())))
Assignment operator.
Definition ref.h:164
void reset(T *ptr, decltype(adopt_ref)) noexcept(noexcept(refcounted_remove_ref(std::declval< T * >())))
Reset the handle with a new value.
Definition ref.h:192
constexpr bool operator!() const noexcept
Check if the handle contains no pointer.
Definition ref.h:218
ref(T *ptr) noexcept(noexcept(refcounted_add_ref(std::declval< T * >())))
Construct a new ref object incrementing the reference count of the passed pointer.
Definition ref.h:147
~ref() noexcept(noexcept(refcounted_remove_ref(std::declval< T * >())))
Destructor.
Definition ref.h:202
constexpr ref() noexcept
Construct an empty ref.
Definition ref.h:133
constexpr T * operator->() const noexcept
Dereference this handle.
Definition ref.h:204
constexpr ref(ref &&other) noexcept
Move constructor.
Definition ref.h:162
constexpr T * get() const noexcept
Get the contained value.
Definition ref.h:208
void reset(T *ptr) noexcept(noexcept(refcounted_add_ref(std::declval< T * >())) &&noexcept(refcounted_remove_ref(std::declval< T * >())))
Reset the handle with a new value.
Definition ref.h:181
ref(T *ptr, decltype(adopt_ref)) noexcept
Construct a new ref object.
Definition ref.h:139
ref & operator=(ref &&other) noexcept(noexcept(refcounted_remove_ref(std::declval< T * >())))
Move assignment operator.
Definition ref.h:171
constexpr T & operator*() const noexcept
Dereference this handle.
Definition ref.h:206
ref(const ref &other) noexcept(noexcept(refcounted_add_ref(std::declval< T * >())))
Construct a new ref object.
Definition ref.h:158
Concept describing a refcount policy for use with intrusive_refcount.
Definition ref.h:17
Concept checking if a type is viable for usage with ref<> (i.e. it provides overloads for refcounted_...
Definition ref.h:116
Threadsafe refcount policy.
Definition ref.h:27
Thread unsafe refcount policy.
Definition ref.h:40