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 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 }
330 atomic_ref& operator=(const atomic_ref<T>& other) noexcept(
331 noexcept(refcounted_add_ref(std::declval<T*>())) && noexcept(refcounted_remove_ref(std::declval<T*>()))) {
332 if (&other != this) exchange(other.load());
333 return *this;
334 }
336 //NOLINTNEXTLINE(hicpp-noexcept-move,performance-noexcept-move-constructor)
337 atomic_ref& operator=(atomic_ref<T>&& other) noexcept(noexcept(refcounted_remove_ref(std::declval<T*>()))) {
338 exchange(ref<T>(other.release(), adopt_ref));
339 return *this;
340 }
342 //NOLINTNEXTLINE(performance-noexcept-destructor)
343 ~atomic_ref() noexcept(noexcept(refcounted_remove_ref(std::declval<T*>()))) { exchange(ref<T>()); }
345 ref<T> load() const noexcept(noexcept(refcounted_add_ref(std::declval<T*>()))) {
346 // Early out if nullptr
347 if ((m_ptr.load(std::memory_order_relaxed) & ~lock_mask) == 0) return 0;
348
349 // Lock the pointer to prevent concurrent modification
350 auto val = lock();
351 // Get original pointer
352 auto ptr = reinterpret_cast<T*>(val);
353 // Add reference
354 if (ptr) refcounted_add_ref(ptr);
355 // Unlock again
356 unlock_with(val);
357 return ref<T>(ptr, adopt_ref);
358 }
364 void store(ref<T> hdl) { exchange(std::move(hdl)); }
369 void store(const atomic_ref<T>& hdl) { exchange(hdl.load()); }
377 auto ptr = hdl.release();
378 if ((reinterpret_cast<uintptr_t>(ptr) & lock_mask) != 0) throw std::logic_error("invalid pointer");
379
380 // Lock the current pointer value
381 auto val = lock();
382 // Unlock again with new value
383 unlock_with(reinterpret_cast<uintptr_t>(ptr));
384 // Return the old pointer without incrementing the reference count
385 return ref<T>(reinterpret_cast<T*>(val), adopt_ref);
386 }
391 ref<T> exchange(const atomic_ref<T>& hdl) { return exchange(hdl.load()); }
393 T* release() noexcept { return exchange(ref<T>()).release(); }
395 void reset() noexcept(noexcept(refcounted_remove_ref(std::declval<T*>()))) { exchange(ref<T>()); }
397 //NOLINTNEXTLINE(google-explicit-constructor)
398 operator bool() const noexcept { return (m_ptr.load(std::memory_order_relaxed) & ~lock_mask) != 0; }
400 bool operator!() const noexcept { return (m_ptr.load(std::memory_order_relaxed) & ~lock_mask) == 0; }
402 ref<T> operator->() const noexcept(noexcept(refcounted_add_ref(std::declval<T*>()))) { return load(); }
403 };
404
405 template<typename T>
406 inline constexpr auto operator<=>(const ref<T>& lhs, const ref<T>& rhs) noexcept {
407 return lhs.get() <=> rhs.get();
408 }
409 template<typename T>
410 inline constexpr auto operator<=>(const ref<T>& lhs, const T* rhs) noexcept {
411 return lhs.get() <=> rhs;
412 }
413 template<typename T>
414 inline constexpr auto operator<=>(const T* lhs, const ref<T>& rhs) noexcept {
415 return lhs <=> rhs.get();
416 }
417
418 template<typename T>
419 inline constexpr auto operator==(const ref<T>& lhs, const ref<T>& rhs) noexcept {
420 return (lhs <=> rhs) == std::strong_ordering::equal;
421 }
422 template<typename T>
423 inline constexpr auto operator==(const ref<T>& lhs, const T* rhs) noexcept {
424 return (lhs <=> rhs) == std::strong_ordering::equal;
425 }
426 template<typename T>
427 inline constexpr auto operator==(const T* lhs, const ref<T>& rhs) noexcept {
428 return (lhs <=> rhs) == std::strong_ordering::equal;
429 }
430 template<typename T>
431 inline constexpr auto operator!=(const ref<T>& lhs, const ref<T>& rhs) noexcept {
432 return (lhs <=> rhs) != std::strong_ordering::equal;
433 }
434 template<typename T>
435 inline constexpr auto operator!=(const ref<T>& lhs, const T* rhs) noexcept {
436 return (lhs <=> rhs) != std::strong_ordering::equal;
437 }
438 template<typename T>
439 inline constexpr auto operator!=(const T* lhs, const ref<T>& rhs) noexcept {
440 return (lhs <=> rhs) != std::strong_ordering::equal;
441 }
442
443 template<typename T>
444 inline constexpr auto operator==(const atomic_ref<T>& lhs, const atomic_ref<T>& rhs) noexcept {
445 return (lhs <=> rhs) == std::strong_ordering::equal;
446 }
447 template<typename T>
448 inline constexpr auto operator==(const atomic_ref<T>& lhs, const T* rhs) noexcept {
449 return (lhs <=> rhs) == std::strong_ordering::equal;
450 }
451 template<typename T>
452 inline constexpr auto operator==(const T* lhs, const atomic_ref<T>& rhs) noexcept {
453 return (lhs <=> rhs) == std::strong_ordering::equal;
454 }
455 template<typename T>
456 inline constexpr auto operator!=(const atomic_ref<T>& lhs, const atomic_ref<T>& rhs) noexcept {
457 return (lhs <=> rhs) != std::strong_ordering::equal;
458 }
459 template<typename T>
460 inline constexpr auto operator!=(const atomic_ref<T>& lhs, const T* rhs) noexcept {
461 return (lhs <=> rhs) != std::strong_ordering::equal;
462 }
463 template<typename T>
464 inline constexpr auto operator!=(const T* lhs, const atomic_ref<T>& rhs) noexcept {
465 return (lhs <=> rhs) != std::strong_ordering::equal;
466 }
467
468 template<typename T, typename U>
469 ref<T> static_ref_cast(const ref<U>& rhs) noexcept {
470 return ref<T>(static_cast<T*>(rhs.get()));
471 }
472 template<typename T, typename U>
473 ref<T> static_ref_cast(ref<U>&& rhs) noexcept {
474 return ref<T>(static_cast<T*>(rhs.release()), adopt_ref);
475 }
476 template<typename T, typename U>
477 ref<T> const_ref_cast(const ref<U>& rhs) noexcept {
478 return ref<T>(const_cast<T*>(rhs.get()));
479 }
480 template<typename T, typename U>
481 ref<T> const_ref_cast(ref<U>&& rhs) noexcept {
482 return ref<T>(const_cast<T*>(rhs.release()), adopt_ref);
483 }
484 template<typename T, typename U>
485 ref<T> dynamic_ref_cast(const ref<U>& rhs) noexcept {
486 return ref<T>(dynamic_cast<T*>(rhs.get()));
487 }
488 template<typename T, typename U>
489 ref<T> dynamic_ref_cast(ref<U>&& rhs) noexcept {
490 auto ptr = dynamic_cast<T*>(rhs.get());
491 if (!ptr) return ref<T>();
492 // We already hold the correct pointer using get(),
493 // so only clear it without removing the ref
494 rhs.release();
495 return ref<T>(ptr, adopt_ref);
496 }
497
498} // namespace asyncpp
499
500template<typename T>
501//NOLINTNEXTLINE(cert-dcl58-cpp)
502struct std::hash<asyncpp::ref<T>> {
503 constexpr size_t operator()(const asyncpp::ref<T>& rhs) const noexcept { return std::hash<void*>{}(rhs.get()); }
504};
505
506template<typename T>
507//NOLINTNEXTLINE(cert-dcl58-cpp)
508struct std::hash<asyncpp::atomic_ref<T>> {
509 constexpr size_t operator()(const asyncpp::atomic_ref<T>& rhs) const noexcept {
510 auto ptr = rhs.m_ptr.load(std::memory_order::relaxed) & ~asyncpp::atomic_ref<T>::lock_mask;
511 return std::hash<uintptr_t>{}(ptr);
512 }
513};
Thread safe reference handle.
Definition ref.h:238
ref< T > exchange(ref< T > hdl)
Reset the handle with a new value.
Definition ref.h:376
ref< T > exchange(const atomic_ref< T > &hdl)
Reset the handle with a new value.
Definition ref.h:391
~atomic_ref() noexcept(noexcept(refcounted_remove_ref(std::declval< T * >())))
Destructor.
Definition ref.h:343
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:395
atomic_ref & operator=(atomic_ref< T > &&other) noexcept(noexcept(refcounted_remove_ref(std::declval< T * >())))
Move assignment operator.
Definition ref.h:337
void store(const atomic_ref< T > &hdl)
Store a new value and destroy the old one.
Definition ref.h:369
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:330
ref< T > load() const noexcept(noexcept(refcounted_add_ref(std::declval< T * >())))
Get the contained value.
Definition ref.h:345
bool operator!() const noexcept
Check if the handle contains no pointer.
Definition ref.h:400
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:393
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:364
ref< T > operator->() const noexcept(noexcept(refcounted_add_ref(std::declval< T * >())))
Dereference this handle.
Definition ref.h:402
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