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(
165 noexcept(refcounted_add_ref(std::declval<T*>())) && 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(
182 noexcept(refcounted_add_ref(std::declval<T*>())) && 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 template<RefCountable T2>
243 friend constexpr auto operator<=>(const atomic_ref<T2>& lhs, const T2* rhs) noexcept;
244 template<RefCountable T2>
245 friend constexpr auto operator<=>(const T2* lhs, const atomic_ref<T2>& rhs) noexcept;
246 mutable std::atomic<uintptr_t> m_ptr;
247
248 static constexpr uintptr_t lock_mask = uintptr_t{1} << (sizeof(uintptr_t) * 8 - 1);
249
254 inline static void cpu_pause() noexcept {
255#if defined(__i386) || defined(_M_IX86) || defined(_X86_) || defined(__amd64) || defined(_M_AMD64)
256#ifdef _MSC_VER
257 _mm_pause();
258#else
259 __builtin_ia32_pause();
260#endif
261#elif defined(__arm__) || defined(_ARM) || defined(_M_ARM) || defined(__arm)
262#ifdef _MSC_VER
263 __yield();
264#else
265 asm volatile("yield");
266#endif
267#elif defined(__riscv)
268 asm volatile("pause");
269#endif
270 }
271
275 uintptr_t lock() const noexcept {
276 // Lock the current pointer value
277 auto val = m_ptr.fetch_or(lock_mask, std::memory_order_acquire);
278 while ((val & lock_mask) == lock_mask) {
279 cpu_pause();
280 val = m_ptr.fetch_or(lock_mask, std::memory_order_acquire);
281 }
282 return val;
283 }
284
288 void unlock_with(uintptr_t val) const noexcept {
289 assert((val & lock_mask) == 0);
290 [[maybe_unused]] auto res = m_ptr.exchange(val, std::memory_order_release);
291 assert((res & lock_mask) == lock_mask);
292 }
293
294 public:
298 constexpr atomic_ref() noexcept : m_ptr(0) {}
305 // NOLINTNEXTLINE(google-explicit-constructor)
307 if ((reinterpret_cast<uintptr_t>(ptr.get()) & lock_mask) != 0) throw std::logic_error("invalid pointer");
308 m_ptr = reinterpret_cast<uintptr_t>(ptr.release());
309 }
311 atomic_ref& operator=(const ref<T>& other) {
312 exchange(other);
313 return *this;
314 }
317 exchange(ref<T>(other.release(), adopt_ref));
318 return *this;
319 }
321 atomic_ref& operator=(const atomic_ref<T>& other) noexcept(
322 noexcept(refcounted_add_ref(std::declval<T*>())) && noexcept(refcounted_remove_ref(std::declval<T*>()))) {
323 if (&other != this) exchange(other.load());
324 return *this;
325 }
327 //NOLINTNEXTLINE(hicpp-noexcept-move,performance-noexcept-move-constructor)
328 atomic_ref& operator=(atomic_ref<T>&& other) noexcept(noexcept(refcounted_remove_ref(std::declval<T*>()))) {
329 exchange(ref<T>(other.release(), adopt_ref));
330 return *this;
331 }
333 //NOLINTNEXTLINE(performance-noexcept-destructor)
334 ~atomic_ref() noexcept(noexcept(refcounted_remove_ref(std::declval<T*>()))) { exchange(ref<T>()); }
336 ref<T> load() const noexcept(noexcept(refcounted_add_ref(std::declval<T*>()))) {
337 // Early out if nullptr
338 if ((m_ptr.load(std::memory_order_relaxed) & ~lock_mask) == 0) return 0;
339
340 // Lock the pointer to prevent concurrent modification
341 auto val = lock();
342 // Get original pointer
343 auto ptr = reinterpret_cast<T*>(val);
344 // Add reference
345 if (ptr) refcounted_add_ref(ptr);
346 // Unlock again
347 unlock_with(val);
348 return ref<T>(ptr, adopt_ref);
349 }
355 void store(ref<T> hdl) { exchange(std::move(hdl)); }
360 void store(const atomic_ref<T>& hdl) { exchange(hdl.load()); }
368 auto ptr = hdl.release();
369 if ((reinterpret_cast<uintptr_t>(ptr) & lock_mask) != 0) throw std::logic_error("invalid pointer");
370
371 // Lock the current pointer value
372 auto val = lock();
373 // Unlock again with new value
374 unlock_with(reinterpret_cast<uintptr_t>(ptr));
375 // Return the old pointer without incrementing the reference count
376 return ref<T>(reinterpret_cast<T*>(val), adopt_ref);
377 }
382 ref<T> exchange(const atomic_ref<T>& hdl) { return exchange(hdl.load()); }
384 T* release() noexcept { return exchange(ref<T>()).release(); }
386 void reset() noexcept(noexcept(refcounted_remove_ref(std::declval<T*>()))) { exchange(ref<T>()); }
388 //NOLINTNEXTLINE(google-explicit-constructor)
389 operator bool() const noexcept { return (m_ptr.load(std::memory_order_relaxed) & ~lock_mask) != 0; }
391 bool operator!() const noexcept { return (m_ptr.load(std::memory_order_relaxed) & ~lock_mask) == 0; }
393 ref<T> operator->() const noexcept(noexcept(refcounted_add_ref(std::declval<T*>()))) { return load(); }
394 };
395
396 template<typename T>
397 inline constexpr auto operator<=>(const ref<T>& lhs, const ref<T>& rhs) noexcept {
398 return lhs.get() <=> rhs.get();
399 }
400 template<typename T>
401 inline constexpr auto operator<=>(const ref<T>& lhs, const T* rhs) noexcept {
402 return lhs.get() <=> rhs;
403 }
404 template<typename T>
405 inline constexpr auto operator<=>(const T* lhs, const ref<T>& rhs) noexcept {
406 return lhs <=> rhs.get();
407 }
408
409 template<typename T>
410 inline constexpr auto operator==(const ref<T>& lhs, const ref<T>& rhs) noexcept {
411 return (lhs <=> rhs) == std::strong_ordering::equal;
412 }
413 template<typename T>
414 inline constexpr auto operator==(const ref<T>& lhs, const T* rhs) noexcept {
415 return (lhs <=> rhs) == std::strong_ordering::equal;
416 }
417 template<typename T>
418 inline constexpr auto operator==(const T* lhs, const ref<T>& rhs) noexcept {
419 return (lhs <=> rhs) == std::strong_ordering::equal;
420 }
421 template<typename T>
422 inline constexpr auto operator!=(const ref<T>& lhs, const ref<T>& rhs) noexcept {
423 return (lhs <=> rhs) != std::strong_ordering::equal;
424 }
425 template<typename T>
426 inline constexpr auto operator!=(const ref<T>& lhs, const T* rhs) noexcept {
427 return (lhs <=> rhs) != std::strong_ordering::equal;
428 }
429 template<typename T>
430 inline constexpr auto operator!=(const T* lhs, const ref<T>& rhs) noexcept {
431 return (lhs <=> rhs) != std::strong_ordering::equal;
432 }
433
434 template<RefCountable T>
435 inline constexpr auto operator<=>(const atomic_ref<T>& lhs, const atomic_ref<T>& rhs) noexcept {
436 return (lhs.m_ptr.load(std::memory_order::relaxed) & ~atomic_ref<T>::lock_mask) <=>
437 (rhs.m_ptr.load(std::memory_order::relaxed) & ~atomic_ref<T>::lock_mask);
438 }
439 template<RefCountable T>
440 inline constexpr auto operator<=>(const atomic_ref<T>& lhs, const T* rhs) noexcept {
441 return (lhs.m_ptr.load(std::memory_order::relaxed) & ~atomic_ref<T>::lock_mask) <=>
442 reinterpret_cast<uintptr_t>(rhs);
443 }
444 template<RefCountable T>
445 inline constexpr auto operator<=>(const T* lhs, const atomic_ref<T>& rhs) noexcept {
446 return reinterpret_cast<uintptr_t>(lhs) <=>
447 (rhs.m_ptr.load(std::memory_order::relaxed) & ~atomic_ref<T>::lock_mask);
448 }
449
450 template<typename T>
451 inline constexpr auto operator==(const atomic_ref<T>& lhs, const atomic_ref<T>& rhs) noexcept {
452 return (lhs <=> rhs) == std::strong_ordering::equal;
453 }
454 template<typename T>
455 inline constexpr auto operator==(const atomic_ref<T>& lhs, const T* rhs) noexcept {
456 return (lhs <=> rhs) == std::strong_ordering::equal;
457 }
458 template<typename T>
459 inline constexpr auto operator==(const T* lhs, const atomic_ref<T>& rhs) noexcept {
460 return (lhs <=> rhs) == std::strong_ordering::equal;
461 }
462 template<typename T>
463 inline constexpr auto operator!=(const atomic_ref<T>& lhs, const atomic_ref<T>& rhs) noexcept {
464 return (lhs <=> rhs) != std::strong_ordering::equal;
465 }
466 template<typename T>
467 inline constexpr auto operator!=(const atomic_ref<T>& lhs, const T* rhs) noexcept {
468 return (lhs <=> rhs) != std::strong_ordering::equal;
469 }
470 template<typename T>
471 inline constexpr auto operator!=(const T* lhs, const atomic_ref<T>& rhs) noexcept {
472 return (lhs <=> rhs) != std::strong_ordering::equal;
473 }
474
475 template<typename T, typename U>
476 ref<T> static_ref_cast(const ref<U>& rhs) noexcept {
477 return ref<T>(static_cast<T*>(rhs.get()));
478 }
479 template<typename T, typename U>
480 ref<T> static_ref_cast(ref<U>&& rhs) noexcept {
481 return ref<T>(static_cast<T*>(rhs.release()), adopt_ref);
482 }
483 template<typename T, typename U>
484 ref<T> const_ref_cast(const ref<U>& rhs) noexcept {
485 return ref<T>(const_cast<T*>(rhs.get()));
486 }
487 template<typename T, typename U>
488 ref<T> const_ref_cast(ref<U>&& rhs) noexcept {
489 return ref<T>(const_cast<T*>(rhs.release()), adopt_ref);
490 }
491 template<typename T, typename U>
492 ref<T> dynamic_ref_cast(const ref<U>& rhs) noexcept {
493 return ref<T>(dynamic_cast<T*>(rhs.get()));
494 }
495 template<typename T, typename U>
496 ref<T> dynamic_ref_cast(ref<U>&& rhs) noexcept {
497 auto ptr = dynamic_cast<T*>(rhs.get());
498 if (!ptr) return ref<T>();
499 // We already hold the correct pointer using get(),
500 // so only clear it without removing the ref
501 rhs.release();
502 return ref<T>(ptr, adopt_ref);
503 }
504
505} // namespace asyncpp
506
507template<typename T>
508//NOLINTNEXTLINE(cert-dcl58-cpp)
509struct std::hash<asyncpp::ref<T>> {
510 constexpr size_t operator()(const asyncpp::ref<T>& rhs) const noexcept { return std::hash<void*>{}(rhs.get()); }
511};
512
513template<typename T>
514//NOLINTNEXTLINE(cert-dcl58-cpp)
515struct std::hash<asyncpp::atomic_ref<T>> {
516 constexpr size_t operator()(const asyncpp::atomic_ref<T>& rhs) const noexcept {
517 auto ptr = rhs.m_ptr.load(std::memory_order::relaxed) & ~asyncpp::atomic_ref<T>::lock_mask;
518 return std::hash<uintptr_t>{}(ptr);
519 }
520};
Thread safe reference handle.
Definition ref.h:238
ref< T > exchange(ref< T > hdl)
Reset the handle with a new value.
Definition ref.h:367
ref< T > exchange(const atomic_ref< T > &hdl)
Reset the handle with a new value.
Definition ref.h:382
~atomic_ref() noexcept(noexcept(refcounted_remove_ref(std::declval< T * >())))
Destructor.
Definition ref.h:334
atomic_ref(ref< T > ptr)
Construct a new atomic_ref object.
Definition ref.h:306
void reset() noexcept(noexcept(refcounted_remove_ref(std::declval< T * >())))
Reset the pointer to nullptr.
Definition ref.h:386
atomic_ref & operator=(atomic_ref< T > &&other) noexcept(noexcept(refcounted_remove_ref(std::declval< T * >())))
Move assignment operator.
Definition ref.h:328
void store(const atomic_ref< T > &hdl)
Store a new value and destroy the old one.
Definition ref.h:360
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:321
ref< T > load() const noexcept(noexcept(refcounted_add_ref(std::declval< T * >())))
Get the contained value.
Definition ref.h:336
bool operator!() const noexcept
Check if the handle contains no pointer.
Definition ref.h:391
atomic_ref & operator=(ref< T > &&other)
Move assignment operator.
Definition ref.h:316
atomic_ref & operator=(const ref< T > &other)
Assignment operator.
Definition ref.h:311
T * release() noexcept
Release the contained pointer.
Definition ref.h:384
constexpr atomic_ref() noexcept
Construct an empty atomic_ref.
Definition ref.h:298
void store(ref< T > hdl)
Store a new value and destroy the old one.
Definition ref.h:355
ref< T > operator->() const noexcept(noexcept(refcounted_add_ref(std::declval< T * >())))
Dereference this handle.
Definition ref.h:393
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