Helios Engine
A modular ECS based data-oriented C++23 game engine framework
Loading...
Searching...
No Matches
ref_counted.hpp
Go to the documentation of this file.
1#pragma once
2
3#include <helios/assert.hpp>
4
5#include <atomic>
6#include <concepts>
7#include <cstdint>
8#include <memory>
9#include <memory_resource>
10#include <type_traits>
11
12namespace helios::mem {
13
14template <typename Derived>
15class RcFromThis;
16
17template <typename Derived>
18class ArcFromThis;
19
20template <typename Derived, typename Allocator = std::allocator<Derived>>
21 requires std::derived_from<Derived, RcFromThis<Derived>>
22class RefCounted;
23
24template <typename Derived, typename Allocator = std::allocator<Derived>>
25 requires std::derived_from<Derived, ArcFromThis<Derived>>
27
28/**
29 * @brief CRTP base class that embeds a non-atomic reference counter into the
30 * derived type.
31 * @details Inherit from this class to make a type usable with `RefCounted<T>`.
32 * The counter tracks the number of `RefCounted` handles pointing to the object.
33 * The counter is NOT thread-safe; use `ArcFromThis` if thread-safety is
34 * required.
35 *
36 * The counter starts at 0.
37 * `RefCounted<T>` increments it on construction and decrements it on
38 * destruction, deleting the object when it reaches 0.
39 * @warning Do not copy or move `RcFromThis` directly;
40 * copies share the same counter slot but should be managed exclusively through
41 * `RefCounted<T>`.
42 * @tparam Derived The concrete class inheriting from this base
43 *
44 * @code
45 * class Mesh final : public helios::mem::RcFromThis<Mesh> {
46 * public:
47 * explicit Mesh(int id) : id_(id) {}
48 * int Id() const { return id_; }
49 * private:
50 * int id_;
51 * };
52 *
53 * auto mesh = helios::mem::MakeRc<Mesh>(42);
54 * @endcode
55 */
56template <typename Derived>
58public:
59 /// @brief Default-constructs with reference count zero.
60 RcFromThis() noexcept = default;
61
62 /// @brief Copy-constructs; does not copy the reference count.
63 RcFromThis(const RcFromThis& /*other*/) noexcept {}
64
65 /// @brief Move-constructs; does not transfer the reference count.
66 RcFromThis(RcFromThis&& /*other*/) noexcept {}
67
68 /// @brief Destroys the embedded counter; does not delete the derived object.
69 ~RcFromThis() noexcept = default;
70
71 /// @brief Copy-assigns; does not affect the reference count.
72 RcFromThis& operator=(const RcFromThis& /*other*/) noexcept { return *this; }
73
74 /// @brief Move-assigns; does not affect the reference count.
75 RcFromThis& operator=(RcFromThis&& /*other*/) noexcept { return *this; }
76
77 /**
78 * @brief Returns the current reference count.
79 * @return Current number of `RefCounted` handles owning this object
80 */
81 [[nodiscard]] uint32_t RefCount() const noexcept { return ref_count_; }
82
83private:
84 template <typename D, typename A>
85 requires std::derived_from<D, RcFromThis<D>>
86 friend class RefCounted;
87
88 /// @brief Increments the reference count by 1.
89 void AddRef() noexcept { ++ref_count_; }
90
91 /**
92 * @brief Decrements the reference count and checks if it reached zero.
93 * @warning Triggers an assertion if called when the ref count is already
94 * zero.
95 * @return true if the object should be deleted (ref count reached 0)
96 */
97 [[nodiscard]] bool Release() noexcept;
98
99 uint32_t ref_count_ = 0;
100};
101
102template <typename Derived>
103inline bool RcFromThis<Derived>::Release() noexcept {
104 HELIOS_ASSERT(ref_count_ > 0,
105 "Release() called on object with zero ref count!");
106 return --ref_count_ == 0;
107}
108
109/**
110 * @brief CRTP base class that embeds an atomic reference counter into the
111 * derived type.
112 * @details Inherit from this class to make a type usable with
113 * `AtomicRefCounted<T>`. The counter tracks the number of `AtomicRefCounted`
114 * handles pointing to the object. All counter operations use
115 * `std::memory_order_acq_rel` / `std::memory_order_acquire` to ensure correct
116 * visibility across threads.
117 * @note The counter starts at 0. `AtomicRefCounted<T>` increments it on
118 * construction and decrements it on destruction, deleting the object when it
119 * reaches 0.
120 * @warning Do not manage the same raw pointer from multiple threads without
121 * using `AtomicRefCounted<T>` handles exclusively.
122 * @tparam Derived The concrete class inheriting from this base
123 *
124 * @code
125 * class Texture final : public helios::mem::ArcFromThis<Texture> {
126 * public:
127 * explicit Texture(std::string_view name) : name_(name) {}
128 * std::string_view Name() const { return name_; }
129 * private:
130 * std::string name_;
131 * };
132 *
133 * auto tex = helios::mem::MakeArc<Texture>("diffuse");
134 * @endcode
135 */
136template <typename Derived>
138public:
139 /// @brief Default-constructs with reference count zero.
140 ArcFromThis() noexcept = default;
141
142 /// @brief Copy-constructs; does not copy the reference count.
143 ArcFromThis(const ArcFromThis& /*other*/) noexcept {}
144
145 /// @brief Move-constructs; does not transfer the reference count.
146 ArcFromThis(ArcFromThis&& /*other*/) noexcept {}
147
148 /// @brief Destroys the embedded counter; does not delete the derived object.
149 ~ArcFromThis() noexcept = default;
150
151 /// @brief Copy-assigns; does not affect the reference count.
152 ArcFromThis& operator=(const ArcFromThis& /*other*/) noexcept {
153 return *this;
154 }
155
156 /// @brief Move-assigns; does not affect the reference count.
157 ArcFromThis& operator=(ArcFromThis&& /*other*/) noexcept { return *this; }
158
159 /**
160 * @brief Returns the current reference count.
161 * @note This is a snapshot; the value may change immediately after the call
162 * in a multithreaded context.
163 * @return Current number of `AtomicRefCounted` handles owning this object
164 */
165 [[nodiscard]] uint32_t RefCount() const noexcept {
166 return ref_count_.load(std::memory_order_acquire);
167 }
168
169private:
170 template <typename D, typename A>
171 requires std::derived_from<D, ArcFromThis<D>>
172 friend class AtomicRefCounted;
173
174 /// @brief Atomically increments the reference count by 1.
175 void AddRef() noexcept { ref_count_.fetch_add(1, std::memory_order_relaxed); }
176
177 /**
178 * @brief Atomically decrements the reference count and checks if it reached
179 * zero.
180 * @warning Triggers an assertion if called when the ref count is already
181 * zero.
182 * @return true if the object should be deleted (ref count reached 0)
183 */
184 [[nodiscard]] bool Release() noexcept;
185
186 std::atomic<uint32_t> ref_count_{0};
187};
188
189template <typename Derived>
190inline bool ArcFromThis<Derived>::Release() noexcept {
191 HELIOS_ASSERT(ref_count_.load(std::memory_order_relaxed) > 0,
192 "Release() called on object with zero ref count!");
193 return ref_count_.fetch_sub(1, std::memory_order_acq_rel) == 1;
194}
195
196/**
197 * @brief Non-atomic intrusive reference-counted smart pointer.
198 * @details Manages the lifetime of an object that inherits from
199 * `RcFromThis<T>`. The reference counter lives inside the object itself (no
200 * separate control block).
201 *
202 * Semantics are similar to `std::shared_ptr` but:
203 * - No heap allocation for the control block.
204 * - NOT thread-safe. Use `AtomicRefCounted` / `Arc` for shared ownership across
205 * threads.
206 * - The managed object MUST inherit from `RcFromThis<T>`.
207 *
208 * @note A null `RefCounted<T>` is valid and comparable.
209 * @warning Not thread-safe. Do NOT share a single `RefCounted<T>` instance
210 * across threads. Each thread must hold its own copy of the handle.
211 * @tparam Derived Concrete type managed by this handle; must inherit
212 * `RcFromThis<Derived>`
213 * @tparam Allocator Allocator type used for construction and destruction of the
214 * managed object (defaults to `std::allocator<Derived>`)
215 *
216 * @code
217 * // Default allocator
218 * auto mesh = helios::mem::MakeRc<Mesh>(42);
219 *
220 * // Custom pool allocator
221 * auto mesh = helios::mem::MakeRc<Mesh, PoolAllocator<Mesh>>(42);
222 * auto copy = mesh; // ref count: 2
223 * mesh.Reset(); // ref count: 1
224 * copy.Reset(); // ref count: 0 → Mesh deleted via
225 * PoolAllocator
226 * @endcode
227 */
228template <typename Derived, typename Allocator>
229 requires std::derived_from<Derived, RcFromThis<Derived>>
231public:
232 using AllocatorType = Allocator;
233
234 /**
235 * @brief Constructs a null handle.
236 * @note Only available when `AllocatorType` is default-constructible.
237 * For stateful allocators, use `RefCounted(AllocatorType)` or `MakeRcWith`.
238 */
239 constexpr RefCounted() noexcept
240 requires std::default_initializable<AllocatorType>
241 = default;
242
243 /**
244 * @brief Constructs a null handle storing a pre-built allocator.
245 * @details Use this when the allocator is stateful and you need a typed null
246 * handle that is ready to receive an assignment from a `MakeRcWith`-created
247 * handle.
248 */
249 constexpr explicit RefCounted(AllocatorType alloc) noexcept
250 : alloc_(std::move(alloc)) {}
251
252 /// @brief Constructs a null handle explicitly (default-constructible
253 /// allocators only).
254 constexpr explicit RefCounted(std::nullptr_t) noexcept
255 requires std::default_initializable<AllocatorType>
256 {}
257
258 /// @brief Constructs a null handle explicitly with an explicit allocator.
259 constexpr RefCounted(std::nullptr_t, AllocatorType alloc) noexcept
260 : alloc_(std::move(alloc)) {}
261
262 /**
263 * @brief Constructs a null handle with a PMR memory resource.
264 * @details Only available when `AllocatorType` is
265 * `std::pmr::polymorphic_allocator<T>`.
266 * @param resource Polymorphic memory resource to use for allocations
267 */
268 constexpr explicit RefCounted(std::pmr::memory_resource* resource) noexcept
269 requires std::same_as<AllocatorType,
270 std::pmr::polymorphic_allocator<Derived>>
271 : alloc_(resource) {}
272
273 /**
274 * @brief Constructs from a raw pointer, taking ownership, with a PMR memory
275 * resource.
276 * @details Only available when `AllocatorType` is
277 * `std::pmr::polymorphic_allocator<T>`.
278 * @param ptr Raw pointer to take ownership of (may be `nullptr`)
279 * @param resource Polymorphic memory resource to use for destruction
280 */
281 RefCounted(Derived* ptr, std::pmr::memory_resource* resource) noexcept
282 requires std::same_as<AllocatorType,
283 std::pmr::polymorphic_allocator<Derived>>
284 : RefCounted(ptr, std::pmr::polymorphic_allocator<Derived>(resource)) {}
285
286 /**
287 * @brief Constructs from a raw pointer, taking ownership, with an explicit
288 * allocator.
289 * @warning The pointer must have been allocated with the same allocator and
290 * must not be managed by any other owning handle at the time of this call.
291 * @param ptr Raw pointer to take ownership of (may be `nullptr`)
292 * @param alloc Allocator instance to use for destruction
293 */
294 RefCounted(Derived* ptr, AllocatorType alloc) noexcept;
295
296 /**
297 * @brief Constructs from a raw pointer using a default-constructed allocator.
298 * @note Only available when `AllocatorType` is default-constructible.
299 */
300 explicit RefCounted(Derived* ptr) noexcept
301 requires std::default_initializable<AllocatorType>
302 : RefCounted(ptr, AllocatorType{}) {}
303
304 /**
305 * @brief Copy-constructs a handle sharing ownership with `other`.
306 * @param other Handle to copy from
307 */
308 RefCounted(const RefCounted& other) noexcept;
309
310 /**
311 * @brief Move-constructs a handle transferring ownership from `other`.
312 * @param other Handle to move from; left null after the operation
313 */
314 RefCounted(RefCounted&& other) noexcept;
315
316 // NOLINTBEGIN(hicpp-explicit-conversions)
317 // NOLINTBEGIN(google-explicit-constructor)
318
319 /**
320 * @brief Converting copy constructor from a compatible (derived) type.
321 * @tparam Other Type convertible to `Derived*`
322 * @param other Handle to copy from
323 */
324 template <typename Other>
325 requires std::convertible_to<Other*, Derived*>
327
328 /**
329 * @brief Converting move constructor from a compatible (derived) type.
330 * @tparam Other Type convertible to `Derived*`
331 * @param other Handle to move from
332 */
333 template <typename Other>
334 requires std::convertible_to<Other*, Derived*>
336
337 // NOLINTEND(google-explicit-constructor)
338 // NOLINTEND(hicpp-explicit-conversions)
339
340 /// @brief Destroys the handle and decrements the reference count.
341 ~RefCounted() noexcept { DecRef(); }
342
343 /**
344 * @brief Copy-assigns shared ownership from `other`.
345 * @param other Handle to copy from
346 * @return Reference to this handle
347 */
348 RefCounted& operator=(const RefCounted& other) noexcept;
349
350 /**
351 * @brief Move-assigns ownership from `other`.
352 * @param other Handle to move from; left null after the operation
353 * @return Reference to this handle
354 */
355 RefCounted& operator=(RefCounted&& other) noexcept;
356
357 /**
358 * @brief Converting copy-assignment from a compatible (derived) handle.
359 * @tparam Other Type convertible to `Derived*`
360 * @param other Handle to copy from
361 * @return Reference to this handle
362 */
363 template <typename Other>
364 requires std::convertible_to<Other*, Derived*>
366
367 /**
368 * @brief Converting move-assignment from a compatible (derived) handle.
369 * @tparam Other Type convertible to `Derived*`
370 * @param other Handle to move from; left null after the operation
371 * @return Reference to this handle
372 */
373 template <typename Other>
374 requires std::convertible_to<Other*, Derived*>
376
377 /**
378 * @brief Assigns null, releasing any currently managed object.
379 * @return Reference to this handle
380 */
381 RefCounted& operator=(std::nullptr_t) noexcept;
382
383 /**
384 * @brief Releases ownership and resets the handle to null.
385 * @details Decrements the ref count; destroys the object via the stored
386 * allocator if it reaches 0.
387 */
388 void Reset() noexcept;
389
390 /**
391 * @brief Releases ownership without decrementing the ref count.
392 * @details The caller becomes responsible for managing the object's lifetime.
393 * @return Raw pointer that was managed, or `nullptr`
394 */
395 [[nodiscard]] Derived* Release() noexcept;
396
397 /**
398 * @brief Dereferences the managed object.
399 * @warning Triggers assertion when the handle is null.
400 * @return Reference to the managed object
401 */
402 [[nodiscard]] Derived& operator*() const noexcept;
403
404 /**
405 * @brief Accesses the managed object through pointer syntax.
406 * @warning Triggers assertion when the handle is null.
407 * @return Pointer to the managed object
408 */
409 [[nodiscard]] Derived* operator->() const noexcept;
410
411 /**
412 * @brief Checks whether the handle owns an object.
413 * @return True when non-null, false otherwise
414 */
415 [[nodiscard]] explicit operator bool() const noexcept {
416 return ptr_ != nullptr;
417 }
418
419 /**
420 * @brief Compares handle identity by managed pointer address.
421 * @param other Handle to compare against
422 * @return True when both handles refer to the same object
423 */
424 [[nodiscard]] bool operator==(const RefCounted& other) const noexcept {
425 return ptr_ == other.ptr_;
426 }
427
428 /**
429 * @brief Checks whether the handle is null.
430 * @return True when no object is managed
431 */
432 [[nodiscard]] bool operator==(std::nullptr_t) const noexcept {
433 return ptr_ == nullptr;
434 }
435
436 /**
437 * @brief Compares handle identity against a compatible derived handle.
438 * @tparam Other Derived type convertible to `Derived*`
439 * @param other Handle to compare against
440 * @return True when both handles refer to the same object
441 */
442 template <typename Other>
443 [[nodiscard]] bool operator==(
444 const RefCounted<Other, Allocator>& other) const noexcept {
445 return ptr_ == other.Get();
446 }
447
448 /**
449 * @brief Checks if this handle is the sole owner of the managed object.
450 * @return true if ref count == 1, false if null or ref count > 1
451 */
452 [[nodiscard]] bool Unique() const noexcept { return RefCount() == 1; }
453
454 /**
455 * @brief Checks if the handle is null.
456 * @return true if no object is managed
457 */
458 [[nodiscard]] bool Empty() const noexcept { return ptr_ == nullptr; }
459
460 /**
461 * @brief Returns the raw pointer without releasing ownership.
462 * @return Raw pointer, or `nullptr` if the handle is null
463 */
464 [[nodiscard]] Derived* Get() const noexcept { return ptr_; }
465
466 /**
467 * @brief Returns the current reference count.
468 * @warning Returns 0 if the handle is null.
469 * @return Current reference count, or 0 for a null handle
470 */
471 [[nodiscard]] uint32_t RefCount() const noexcept {
472 return ptr_ != nullptr ? ptr_->RcFromThis<Derived>::RefCount() : 0;
473 }
474
475 /**
476 * @brief Returns a reference to the stored allocator.
477 * @return The allocator instance used for this handle
478 */
479 [[nodiscard]] const AllocatorType& GetAllocator() const noexcept {
480 return alloc_;
481 }
482
483private:
484 // EBO: inherit from AllocatorType so stateless allocators add zero bytes
485 // We use a compressed pair pattern manually since we can't inherit from
486 // Derived* directly. For simplicity, store the allocator as a member —
487 // stateless allocators are typically 1 byte but compilers often optimise them
488 // away in practice. A full EBO wrapper can be substituted here if binary size
489 // is critical.
490 void DecRef() noexcept;
491
492 Derived* ptr_ = nullptr;
493 AllocatorType alloc_;
494};
495
496template <typename Derived, typename Allocator>
497 requires std::derived_from<Derived, RcFromThis<Derived>>
498inline RefCounted<Derived, Allocator>::RefCounted(Derived* ptr,
499 AllocatorType alloc) noexcept
500 : ptr_(ptr), alloc_(std::move(alloc)) {
501 if (ptr_ != nullptr) [[likely]] {
502 ptr_->AddRef();
503 }
504}
505
506template <typename Derived, typename Allocator>
507 requires std::derived_from<Derived, RcFromThis<Derived>>
509 const RefCounted& other) noexcept
510 : ptr_(other.ptr_),
511 alloc_(std::allocator_traits<AllocatorType>::
512 select_on_container_copy_construction(other.alloc_)) {
513 if (ptr_ != nullptr) {
514 ptr_->AddRef();
515 }
516}
517
518template <typename Derived, typename Allocator>
519 requires std::derived_from<Derived, RcFromThis<Derived>>
521 : ptr_(other.ptr_), alloc_(std::move(other.alloc_)) {
522 other.ptr_ = nullptr;
523}
524
525template <typename Derived, typename Allocator>
526 requires std::derived_from<Derived, RcFromThis<Derived>>
527template <typename Other>
528 requires std::convertible_to<Other*, Derived*>
530 const RefCounted<Other, Allocator>& other) noexcept
531 : ptr_(other.Get()),
532 alloc_(std::allocator_traits<AllocatorType>::
533 select_on_container_copy_construction(other.GetAllocator())) {
534 if (ptr_ != nullptr) {
535 ptr_->AddRef();
536 }
537}
538
539template <typename Derived, typename Allocator>
540 requires std::derived_from<Derived, RcFromThis<Derived>>
541template <typename Other>
542 requires std::convertible_to<Other*, Derived*>
544 RefCounted<Other, Allocator>&& other) noexcept
545 : ptr_(other.Release()), alloc_(std::move(other.GetAllocator())) {}
546
547template <typename Derived, typename Allocator>
548 requires std::derived_from<Derived, RcFromThis<Derived>>
551 if (this != &other) [[likely]] {
552 DecRef();
553 if constexpr (std::allocator_traits<AllocatorType>::
554 propagate_on_container_copy_assignment::value) {
555 alloc_ = other.alloc_;
556 }
557 ptr_ = other.ptr_;
558 if (ptr_ != nullptr) {
559 ptr_->AddRef();
560 }
561 }
562 return *this;
563}
564
565template <typename Derived, typename Allocator>
566 requires std::derived_from<Derived, RcFromThis<Derived>>
569 if (this != &other) [[likely]] {
570 DecRef();
571 if constexpr (std::allocator_traits<AllocatorType>::
572 propagate_on_container_move_assignment::value) {
573 alloc_ = std::move(other.alloc_);
574 }
575 ptr_ = other.ptr_;
576 other.ptr_ = nullptr;
577 }
578 return *this;
579}
580
581template <typename Derived, typename Allocator>
582 requires std::derived_from<Derived, RcFromThis<Derived>>
583template <typename Other>
584 requires std::convertible_to<Other*, Derived*>
587 const RefCounted<Other, Allocator>& other) noexcept {
588 DecRef();
589 if constexpr (std::allocator_traits<AllocatorType>::
590 propagate_on_container_copy_assignment::value) {
591 alloc_ = other.GetAllocator();
592 }
593 ptr_ = other.Get();
594 if (ptr_ != nullptr) {
595 ptr_->AddRef();
596 }
597 return *this;
598}
599
600template <typename Derived, typename Allocator>
601 requires std::derived_from<Derived, RcFromThis<Derived>>
602template <typename Other>
603 requires std::convertible_to<Other*, Derived*>
606 RefCounted<Other, Allocator>&& other) noexcept {
607 DecRef();
608 if constexpr (std::allocator_traits<AllocatorType>::
609 propagate_on_container_move_assignment::value) {
610 alloc_ = std::move(other.GetAllocator());
611 }
612 ptr_ = other.Release();
613 return *this;
614}
615
616template <typename Derived, typename Allocator>
617 requires std::derived_from<Derived, RcFromThis<Derived>>
620 Reset();
621 return *this;
622}
623
624template <typename Derived, typename Allocator>
625 requires std::derived_from<Derived, RcFromThis<Derived>>
627 DecRef();
628 ptr_ = nullptr;
629}
630
631template <typename Derived, typename Allocator>
632 requires std::derived_from<Derived, RcFromThis<Derived>>
633inline Derived* RefCounted<Derived, Allocator>::Release() noexcept {
634 auto* raw = ptr_;
635 ptr_ = nullptr;
636 return raw;
637}
638
639template <typename Derived, typename Allocator>
640 requires std::derived_from<Derived, RcFromThis<Derived>>
641inline Derived& RefCounted<Derived, Allocator>::operator*() const noexcept {
642 HELIOS_ASSERT(ptr_ != nullptr, "Dereferencing null RefCounted!");
643 return *ptr_;
644}
645
646template <typename Derived, typename Allocator>
647 requires std::derived_from<Derived, RcFromThis<Derived>>
648inline Derived* RefCounted<Derived, Allocator>::operator->() const noexcept {
649 HELIOS_ASSERT(ptr_ != nullptr, "Dereferencing null RefCounted!");
650 return ptr_;
651}
652
653template <typename Derived, typename Allocator>
654 requires std::derived_from<Derived, RcFromThis<Derived>>
655inline void RefCounted<Derived, Allocator>::DecRef() noexcept {
656 if (ptr_ != nullptr && ptr_->Release()) {
657 std::allocator_traits<AllocatorType>::destroy(alloc_, ptr_);
658 std::allocator_traits<AllocatorType>::deallocate(alloc_, ptr_, 1);
659 ptr_ = nullptr;
660 }
661}
662
663/**
664 * @brief Thread-safe intrusive reference-counted smart pointer.
665 * @details Manages the lifetime of an object that inherits from
666 * `ArcFromThis<T>`. The reference counter lives inside the object itself (no
667 * separate control block).
668 *
669 * Semantics are similar to `std::shared_ptr` but:
670 * - No heap allocation for the control block.
671 * - Thread-safe reference counting via `std::atomic<uint32_t>`.
672 * - The managed object MUST inherit from `ArcFromThis<T>`.
673 *
674 * @note A null `AtomicRefCounted<T>` is valid and comparable.
675 * Copying/moving the handle itself is NOT atomic.
676 * If multiple threads need to share/copy the same handle concurrently, protect
677 * the handle with an external lock. What IS thread-safe is having separate
678 * `AtomicRefCounted` instances across threads pointing to the same object.
679 * @tparam Derived Concrete type managed by this handle; must inherit
680 * `ArcFromThis<Derived>`
681 * @tparam Allocator Allocator type used for construction and destruction of the
682 * managed object (defaults to `std::allocator<Derived>`)
683 *
684 * @code
685 * auto tex = helios::mem::MakeArc<Texture>("diffuse");
686 * auto copy = tex; // thread-safe ref-count increment
687 * // Both tex and copy can be used/destroyed from different threads safely.
688 * @endcode
689 */
690template <typename Derived, typename Allocator>
691 requires std::derived_from<Derived, ArcFromThis<Derived>>
693public:
694 using AllocatorType = Allocator;
695
696 /**
697 * @brief Constructs a null handle.
698 * @note Only available when `AllocatorType` is default-constructible.
699 * For stateful allocators, use `AtomicRefCounted(AllocatorType)` or
700 * `MakeArcWith`.
701 */
702 constexpr AtomicRefCounted() noexcept
703 requires std::default_initializable<AllocatorType>
704 = default;
705
706 /**
707 * @brief Constructs a null handle storing a pre-built allocator.
708 * @details Use this when the allocator is stateful and you need a typed null
709 * handle that is ready to receive an assignment from a `MakeArcWith`-created
710 * handle.
711 */
712 constexpr explicit AtomicRefCounted(AllocatorType alloc) noexcept
713 : alloc_(std::move(alloc)) {}
714
715 /// @brief Constructs a null handle explicitly (default-constructible
716 /// allocators only).
717 constexpr explicit AtomicRefCounted(std::nullptr_t) noexcept
718 requires std::default_initializable<AllocatorType>
719 {}
720
721 /// @brief Constructs a null handle explicitly with an explicit allocator.
722 constexpr AtomicRefCounted(std::nullptr_t, AllocatorType alloc) noexcept
723 : alloc_(std::move(alloc)) {}
724
725 /**
726 * @brief Constructs a null handle with a PMR memory resource.
727 * @details Only available when `AllocatorType` is
728 * `std::pmr::polymorphic_allocator<T>`.
729 * @param resource Polymorphic memory resource to use for allocations
730 */
731 constexpr explicit AtomicRefCounted(
732 std::pmr::memory_resource* resource) noexcept
733 requires std::same_as<AllocatorType,
734 std::pmr::polymorphic_allocator<Derived>>
735 : alloc_(resource) {}
736
737 /**
738 * @brief Constructs from a raw pointer, taking ownership, with a PMR memory
739 * resource.
740 * @details Only available when `AllocatorType` is
741 * `std::pmr::polymorphic_allocator<T>`.
742 * @param ptr Raw pointer to take ownership of (may be `nullptr`)
743 * @param resource Polymorphic memory resource to use for destruction
744 */
745 AtomicRefCounted(Derived* ptr, std::pmr::memory_resource* resource) noexcept
746 requires std::same_as<AllocatorType,
747 std::pmr::polymorphic_allocator<Derived>>
748 : AtomicRefCounted(ptr,
749 std::pmr::polymorphic_allocator<Derived>(resource)) {}
750
751 /**
752 * @brief Constructs from a raw pointer, taking ownership, with an explicit
753 * allocator.
754 * @warning The pointer must have been allocated with the same allocator and
755 * must not be managed by any other owning handle at the time of this call.
756 * @param ptr Raw pointer to take ownership of (may be `nullptr`)
757 * @param alloc Allocator instance to use for destruction
758 */
759 AtomicRefCounted(Derived* ptr, AllocatorType alloc) noexcept;
760
761 /**
762 * @brief Constructs from a raw pointer using a default-constructed allocator.
763 * @note Only available when `AllocatorType` is default-constructible.
764 */
765 explicit AtomicRefCounted(Derived* ptr) noexcept
766 requires std::default_initializable<AllocatorType>
768
769 /**
770 * @brief Copy-constructs a handle sharing ownership with `other`.
771 * @param other Handle to copy from
772 */
773 AtomicRefCounted(const AtomicRefCounted& other) noexcept;
774
775 /**
776 * @brief Move-constructs a handle transferring ownership from `other`.
777 * @param other Handle to move from; left null after the operation
778 */
780
781 // NOLINTBEGIN(hicpp-explicit-conversions)
782 // NOLINTBEGIN(google-explicit-constructor)
783
784 /**
785 * @brief Converting copy constructor from a compatible (derived) type.
786 * @tparam Other Type convertible to `Derived*`
787 * @param other Handle to copy from
788 */
789 template <typename Other>
790 requires std::convertible_to<Other*, Derived*>
792
793 /**
794 * @brief Converting move constructor from a compatible (derived) type.
795 * @tparam Other Type convertible to `Derived*`
796 * @param other Handle to move from
797 */
798 template <typename Other>
799 requires std::convertible_to<Other*, Derived*>
801
802 // NOLINTEND(hicpp-explicit-conversions)
803 // NOLINTEND(google-explicit-constructor)
804
805 /// @brief Destroys the handle and decrements the reference count.
806 ~AtomicRefCounted() noexcept { DecRef(); }
807
808 /**
809 * @brief Copy-assigns shared ownership from `other`.
810 * @param other Handle to copy from
811 * @return Reference to this handle
812 */
814
815 /**
816 * @brief Move-assigns ownership from `other`.
817 * @param other Handle to move from; left null after the operation
818 * @return Reference to this handle
819 */
821
822 /**
823 * @brief Converting copy-assignment from a compatible (derived) handle.
824 * @tparam Other Type convertible to `Derived*`
825 * @param other Handle to copy from
826 * @return Reference to this handle
827 */
828 template <typename Other>
829 requires std::convertible_to<Other*, Derived*>
831 const AtomicRefCounted<Other, Allocator>& other) noexcept;
832
833 /**
834 * @brief Converting move-assignment from a compatible (derived) handle.
835 * @tparam Other Type convertible to `Derived*`
836 * @param other Handle to move from; left null after the operation
837 * @return Reference to this handle
838 */
839 template <typename Other>
840 requires std::convertible_to<Other*, Derived*>
842 AtomicRefCounted<Other, Allocator>&& other) noexcept;
843
844 /**
845 * @brief Assigns null, releasing any currently managed object.
846 * @return Reference to this handle
847 */
848 AtomicRefCounted& operator=(std::nullptr_t) noexcept;
849
850 /**
851 * @brief Releases ownership and resets the handle to null.
852 * @details Decrements the ref count; destroys the object via the stored
853 * allocator if it reaches 0.
854 */
855 void Reset() noexcept;
856
857 /**
858 * @brief Releases ownership without decrementing the ref count.
859 * @details The caller becomes responsible for managing the object's lifetime.
860 * @return Raw pointer that was managed, or `nullptr`
861 */
862 [[nodiscard]] Derived* Release() noexcept;
863
864 /**
865 * @brief Dereferences the managed object.
866 * @warning Triggers assertion when the handle is null.
867 * @return Reference to the managed object
868 */
869 [[nodiscard]] Derived& operator*() const noexcept;
870
871 /**
872 * @brief Accesses the managed object through pointer syntax.
873 * @warning Triggers assertion when the handle is null.
874 * @return Pointer to the managed object
875 */
876 [[nodiscard]] Derived* operator->() const noexcept;
877
878 /**
879 * @brief Checks whether the handle owns an object.
880 * @return True when non-null, false otherwise
881 */
882 [[nodiscard]] explicit operator bool() const noexcept {
883 return ptr_ != nullptr;
884 }
885
886 /**
887 * @brief Compares handle identity by managed pointer address.
888 * @param other Handle to compare against
889 * @return True when both handles refer to the same object
890 */
891 [[nodiscard]] bool operator==(const AtomicRefCounted& other) const noexcept {
892 return ptr_ == other.ptr_;
893 }
894
895 /**
896 * @brief Checks whether the handle is null.
897 * @return True when no object is managed
898 */
899 [[nodiscard]] bool operator==(std::nullptr_t) const noexcept {
900 return ptr_ == nullptr;
901 }
902
903 /**
904 * @brief Compares handle identity against a compatible derived handle.
905 * @tparam Other Derived type convertible to `Derived*`
906 * @param other Handle to compare against
907 * @return True when both handles refer to the same object
908 */
909 template <typename Other>
910 [[nodiscard]] bool operator==(
911 const AtomicRefCounted<Other, Allocator>& other) const noexcept {
912 return ptr_ == other.Get();
913 }
914
915 /**
916 * @brief Checks if this handle is the sole owner of the managed object.
917 * @return true if ref count == 1, false if null or ref count > 1
918 */
919 [[nodiscard]] bool Unique() const noexcept { return RefCount() == 1; }
920
921 /**
922 * @brief Checks if the handle is null.
923 * @return true if no object is managed
924 */
925 [[nodiscard]] bool Empty() const noexcept { return ptr_ == nullptr; }
926
927 /**
928 * @brief Returns the raw pointer without releasing ownership.
929 * @return Raw pointer, or `nullptr` if the handle is null
930 */
931 [[nodiscard]] Derived* Get() const noexcept { return ptr_; }
932
933 /**
934 * @brief Returns the current reference count.
935 * @warning Returns 0 if the handle is null.
936 * @return Current reference count, or 0 for a null handle
937 */
938 [[nodiscard]] uint32_t RefCount() const noexcept {
939 return ptr_ != nullptr ? ptr_->ArcFromThis<Derived>::RefCount() : 0;
940 }
941
942 /**
943 * @brief Returns a reference to the stored allocator.
944 * @return The allocator instance used for this handle
945 */
946 [[nodiscard]] const AllocatorType& GetAllocator() const noexcept {
947 return alloc_;
948 }
949
950private:
951 void DecRef() noexcept;
952
953 Derived* ptr_ = nullptr;
954 AllocatorType alloc_;
955};
956
957template <typename Derived, typename Allocator>
958 requires std::derived_from<Derived, ArcFromThis<Derived>>
959inline AtomicRefCounted<Derived, Allocator>::AtomicRefCounted(
960 Derived* ptr, AllocatorType alloc) noexcept
961 : ptr_(ptr), alloc_(std::move(alloc)) {
962 if (ptr_ != nullptr) {
963 ptr_->AddRef();
964 }
965}
966
967template <typename Derived, typename Allocator>
968 requires std::derived_from<Derived, ArcFromThis<Derived>>
970 const AtomicRefCounted& other) noexcept
971 : ptr_(other.ptr_),
972 alloc_(std::allocator_traits<AllocatorType>::
973 select_on_container_copy_construction(other.alloc_)) {
974 if (ptr_ != nullptr) {
975 ptr_->AddRef();
976 }
977}
978
979template <typename Derived, typename Allocator>
980 requires std::derived_from<Derived, ArcFromThis<Derived>>
982 AtomicRefCounted&& other) noexcept
983 : ptr_(other.ptr_), alloc_(std::move(other.alloc_)) {
984 other.ptr_ = nullptr;
985}
986
987template <typename Derived, typename Allocator>
988 requires std::derived_from<Derived, ArcFromThis<Derived>>
989template <typename Other>
990 requires std::convertible_to<Other*, Derived*>
993 other) noexcept // NOLINT(google-explicit-constructor)
994 : ptr_(other.Get()),
995 alloc_(std::allocator_traits<AllocatorType>::
996 select_on_container_copy_construction(other.GetAllocator())) {
997 if (ptr_ != nullptr) {
998 ptr_->AddRef();
999 }
1000}
1001
1002template <typename Derived, typename Allocator>
1003 requires std::derived_from<Derived, ArcFromThis<Derived>>
1004template <typename Other>
1005 requires std::convertible_to<Other*, Derived*>
1008 other) noexcept // NOLINT(google-explicit-constructor)
1009 : ptr_(other.Release()), alloc_(std::move(other.GetAllocator())) {}
1010
1011template <typename Derived, typename Allocator>
1012 requires std::derived_from<Derived, ArcFromThis<Derived>>
1015 const AtomicRefCounted& other) noexcept {
1016 if (this != &other) [[likely]] {
1017 DecRef();
1018 if constexpr (std::allocator_traits<AllocatorType>::
1019 propagate_on_container_copy_assignment::value) {
1020 alloc_ = other.alloc_;
1021 }
1022 ptr_ = other.ptr_;
1023 if (ptr_ != nullptr) {
1024 ptr_->AddRef();
1025 }
1026 }
1027 return *this;
1028}
1029
1030template <typename Derived, typename Allocator>
1031 requires std::derived_from<Derived, ArcFromThis<Derived>>
1034 AtomicRefCounted&& other) noexcept {
1035 if (this != &other) [[likely]] {
1036 DecRef();
1037 if constexpr (std::allocator_traits<AllocatorType>::
1038 propagate_on_container_move_assignment::value) {
1039 alloc_ = std::move(other.alloc_);
1040 }
1041 ptr_ = other.ptr_;
1042 other.ptr_ = nullptr;
1043 }
1044 return *this;
1045}
1046
1047template <typename Derived, typename Allocator>
1048 requires std::derived_from<Derived, ArcFromThis<Derived>>
1049template <typename Other>
1050 requires std::convertible_to<Other*, Derived*>
1053 const AtomicRefCounted<Other, Allocator>& other) noexcept {
1054 DecRef();
1055 if constexpr (std::allocator_traits<AllocatorType>::
1056 propagate_on_container_copy_assignment::value) {
1057 alloc_ = other.GetAllocator();
1058 }
1059 ptr_ = other.Get();
1060 if (ptr_ != nullptr) {
1061 ptr_->AddRef();
1062 }
1063 return *this;
1064}
1065
1066template <typename Derived, typename Allocator>
1067 requires std::derived_from<Derived, ArcFromThis<Derived>>
1068template <typename Other>
1069 requires std::convertible_to<Other*, Derived*>
1072 AtomicRefCounted<Other, Allocator>&& other) noexcept {
1073 DecRef();
1074 if constexpr (std::allocator_traits<AllocatorType>::
1075 propagate_on_container_move_assignment::value) {
1076 alloc_ = std::move(other.GetAllocator());
1077 }
1078 ptr_ = other.Release();
1079 return *this;
1080}
1081
1082template <typename Derived, typename Allocator>
1083 requires std::derived_from<Derived, ArcFromThis<Derived>>
1086 Reset();
1087 return *this;
1088}
1089
1090template <typename Derived, typename Allocator>
1091 requires std::derived_from<Derived, ArcFromThis<Derived>>
1093 DecRef();
1094 ptr_ = nullptr;
1095}
1096
1097template <typename Derived, typename Allocator>
1098 requires std::derived_from<Derived, ArcFromThis<Derived>>
1100 auto* raw = ptr_;
1101 ptr_ = nullptr;
1102 return raw;
1103}
1104
1105template <typename Derived, typename Allocator>
1106 requires std::derived_from<Derived, ArcFromThis<Derived>>
1108 const noexcept {
1109 HELIOS_ASSERT(ptr_ != nullptr, "Dereferencing null AtomicRefCounted!");
1110 return *ptr_;
1111}
1112
1113template <typename Derived, typename Allocator>
1114 requires std::derived_from<Derived, ArcFromThis<Derived>>
1116 const noexcept {
1117 HELIOS_ASSERT(ptr_ != nullptr, "Dereferencing null AtomicRefCounted!");
1118 return ptr_;
1119}
1120
1121template <typename Derived, typename Allocator>
1122 requires std::derived_from<Derived, ArcFromThis<Derived>>
1123inline void AtomicRefCounted<Derived, Allocator>::DecRef() noexcept {
1124 if (ptr_ != nullptr && ptr_->Release()) {
1125 std::allocator_traits<AllocatorType>::destroy(alloc_, ptr_);
1126 std::allocator_traits<AllocatorType>::deallocate(alloc_, ptr_, 1);
1127 ptr_ = nullptr;
1128 }
1129}
1130
1131/// @brief Alias for `RefCounted<T>` — non-atomic intrusive reference-counted
1132/// handle.
1133template <typename T, typename Allocator = std::allocator<T>>
1135
1136/// @brief Alias for `AtomicRefCounted<T>` — thread-safe intrusive
1137/// reference-counted handle.
1138template <typename T, typename Allocator = std::allocator<T>>
1140
1141/**
1142 * @brief Alias for `RefCounted<T>` using polymorphic memory allocator.
1143 * @details Allows using `std::pmr::memory_resource` for dynamic allocator
1144 * selection.
1145 */
1146template <typename T>
1148
1149/**
1150 * @brief Alias for `AtomicRefCounted<T>` using polymorphic memory allocator.
1151 * @details Thread-safe intrusive reference-counted handle with PMR support.
1152 */
1153template <typename T>
1155
1156/**
1157 * @brief Allocates and constructs a `T` object, returning a `Rc<T,
1158 * std::allocator<T>>` handle.
1159 * @details Preferred way to create `RefCounted` objects.
1160 * The object is allocated via `std::allocator<T>` and owned by the returned
1161 * handle.
1162 * @tparam T Type to construct; must inherit `RcFromThis<T>`
1163 * @tparam Args Constructor argument types
1164 * @param args Arguments forwarded to `T`'s constructor
1165 * @return A new `Rc<T>` with ref count 1
1166 *
1167 * @code
1168 * auto mesh = helios::mem::MakeRc<Mesh>(42);
1169 * @endcode
1170 */
1171template <typename T, typename... Args>
1172 requires std::derived_from<T, RcFromThis<T>> &&
1173 std::constructible_from<T, Args...>
1174[[nodiscard]] inline auto MakeRc(Args&&... args) -> Rc<T> {
1175 using AllocT = std::allocator<T>;
1176 AllocT alloc;
1177 T* ptr = std::allocator_traits<AllocT>::allocate(alloc, 1);
1178 std::allocator_traits<AllocT>::construct(alloc, ptr,
1179 std::forward<Args>(args)...);
1180 return {ptr, std::move(alloc)};
1181}
1182
1183/**
1184 * @brief Allocates and constructs a `T` object, returning an `Arc<T,
1185 * std::allocator<T>>` handle.
1186 * @details Preferred way to create `AtomicRefCounted` objects.
1187 * The object is allocated via `std::allocator<T>` and owned by the returned
1188 * handle.
1189 * @note Only available when `std::allocator<T>` is default-constructible (it
1190 * always is). For stateful allocators (e.g. arena, pool), use `MakeArcWith`
1191 * instead.
1192 * @tparam T Type to construct; must inherit `ArcFromThis<T>`
1193 * @tparam Args Constructor argument types
1194 * @param args Arguments forwarded to `T`'s constructor
1195 * @return A new `Arc<T>` with ref count 1
1196 *
1197 * @code
1198 * auto tex = helios::mem::MakeArc<Texture>("diffuse");
1199 * @endcode
1200 */
1201template <typename T, typename... Args>
1202 requires std::derived_from<T, ArcFromThis<T>> &&
1203 std::constructible_from<T, Args...>
1204[[nodiscard]] inline auto MakeArc(Args&&... args) -> Arc<T> {
1205 using AllocT = std::allocator<T>;
1206 AllocT alloc;
1207 T* ptr = std::allocator_traits<AllocT>::allocate(alloc, 1);
1208 std::allocator_traits<AllocT>::construct(alloc, ptr,
1209 std::forward<Args>(args)...);
1210 return {ptr, std::move(alloc)};
1211}
1212
1213/**
1214 * @brief Overload of `MakeRc` accepting a pre-constructed allocator instance.
1215 * @details Use this when your allocator is stateful and must be initialised
1216 * before the object is created (e.g. a PMR arena).
1217 * @tparam T Type to construct; must inherit `RcFromThis<T>`
1218 * @tparam Allocator Concrete allocator type (e.g. `MyAllocator<T>`)
1219 * @tparam Args Constructor argument types
1220 * @param alloc Allocator instance to use
1221 * @param args Arguments forwarded to `T`'s constructor
1222 * @return A new `Rc<T, Allocator>` with ref count 1
1223 */
1224template <typename T, typename Allocator, typename... Args>
1225 requires std::derived_from<T, RcFromThis<T>> &&
1226 std::constructible_from<T, Args...> &&
1227 (!std::derived_from<std::remove_pointer_t<Allocator>,
1228 std::pmr::memory_resource>)
1229[[nodiscard]] inline auto MakeRcWith(Allocator alloc, Args&&... args)
1230 -> Rc<T, Allocator> {
1231 T* ptr = std::allocator_traits<Allocator>::allocate(alloc, 1);
1232 std::allocator_traits<Allocator>::construct(alloc, ptr,
1233 std::forward<Args>(args)...);
1234 return {ptr, std::move(alloc)};
1235}
1236
1237/**
1238 * @brief Allocates and constructs a `T` object using PMR memory resource,
1239 * returning a `PmrRc<T>` handle.
1240 * @details Convenience overload that wraps `std::pmr::memory_resource*` in a
1241 * `std::pmr::polymorphic_allocator<T>`.
1242 * @tparam T Type to construct; must inherit `RcFromThis<T>`
1243 * @tparam Args Constructor argument types
1244 * @param resource PMR memory resource to use for allocation
1245 * @param args Arguments forwarded to `T`'s constructor
1246 * @return A new `PmrRc<T>` with ref count 1
1247 *
1248 * @code
1249 * auto mesh = helios::mem::MakeRcWith<Mesh>(pmr_resource, 42);
1250 * @endcode
1251 */
1252template <typename T, typename... Args>
1253 requires std::derived_from<T, RcFromThis<T>> &&
1254 std::constructible_from<T, Args...>
1255[[nodiscard]] inline auto MakeRcWith(std::pmr::memory_resource* resource,
1256 Args&&... args) -> PmrRc<T> {
1258 std::pmr::polymorphic_allocator<T>(resource),
1259 std::forward<Args>(args)...);
1260}
1261
1262template <typename T, typename... Args>
1263auto MakeRcWith(std::nullptr_t, Args&&...) -> PmrRc<T> = delete;
1264
1265/**
1266 * @brief Overload of `MakeArc` accepting a pre-constructed allocator instance.
1267 * @details Use this when your allocator is stateful and must be initialised
1268 * before the object is created (e.g. a PMR arena).
1269 * @tparam T Type to construct; must inherit `ArcFromThis<T>`
1270 * @tparam Allocator Concrete allocator type (e.g. `MyAllocator<T>`)
1271 * @tparam Args Constructor argument types
1272 * @param alloc Allocator instance to use
1273 * @param args Arguments forwarded to `T`'s constructor
1274 * @return A new `Arc<T, Allocator>` with ref count 1
1275 */
1276template <typename T, typename Allocator, typename... Args>
1277 requires std::derived_from<T, ArcFromThis<T>> &&
1278 std::constructible_from<T, Args...> &&
1279 (!std::derived_from<std::remove_pointer_t<Allocator>,
1280 std::pmr::memory_resource>)
1281[[nodiscard]] inline auto MakeArcWith(Allocator alloc, Args&&... args)
1283 T* ptr = std::allocator_traits<Allocator>::allocate(alloc, 1);
1284 std::allocator_traits<Allocator>::construct(alloc, ptr,
1285 std::forward<Args>(args)...);
1286 return {ptr, std::move(alloc)};
1287}
1288
1289/**
1290 * @brief Allocates and constructs a `T` object using PMR memory resource,
1291 * returning a `PmrArc<T>` handle.
1292 * @details Convenience overload that wraps `std::pmr::memory_resource*` in a
1293 * `std::pmr::polymorphic_allocator<T>`.
1294 * @tparam T Type to construct; must inherit `ArcFromThis<T>`
1295 * @tparam Args Constructor argument types
1296 * @param resource PMR memory resource to use for allocation
1297 * @param args Arguments forwarded to `T`'s constructor
1298 * @return A new `PmrArc<T>` with ref count 1
1299 *
1300 * @code
1301 * auto tex = helios::mem::MakeArcWith<Texture>(pmr_resource, "diffuse");
1302 * @endcode
1303 */
1304template <typename T, typename... Args>
1305 requires std::derived_from<T, ArcFromThis<T>> &&
1306 std::constructible_from<T, Args...>
1307[[nodiscard]] inline auto MakeArcWith(std::pmr::memory_resource* resource,
1308 Args&&... args) -> PmrArc<T> {
1310 std::pmr::polymorphic_allocator<T>(resource),
1311 std::forward<Args>(args)...);
1312}
1313
1314template <typename T, typename... Args>
1315auto MakeArcWith(std::nullptr_t, Args&&...) -> PmrArc<T> = delete;
1316
1317} // namespace helios::mem
#define HELIOS_ASSERT(condition,...)
Assertion macro that aborts execution in debug builds.
Definition assert.hpp:259
CRTP base class that embeds an atomic reference counter into the derived type.
ArcFromThis() noexcept=default
Default-constructs with reference count zero.
uint32_t RefCount() const noexcept
Returns the current reference count.
ArcFromThis & operator=(ArcFromThis &&) noexcept
Move-assigns; does not affect the reference count.
~ArcFromThis() noexcept=default
Destroys the embedded counter; does not delete the derived object.
ArcFromThis(ArcFromThis &&) noexcept
Move-constructs; does not transfer the reference count.
Thread-safe intrusive reference-counted smart pointer.
constexpr AtomicRefCounted(std::nullptr_t) noexcept
Constructs a null handle explicitly (default-constructible allocators only).
bool operator==(const AtomicRefCounted &other) const noexcept
Compares handle identity by managed pointer address.
void Reset() noexcept
Releases ownership and resets the handle to null.
Derived & operator*() const noexcept
Dereferences the managed object.
AtomicRefCounted & operator=(std::nullptr_t) noexcept
Assigns null, releasing any currently managed object.
bool Empty() const noexcept
Checks if the handle is null.
AtomicRefCounted & operator=(const AtomicRefCounted< Other, Allocator > &other) noexcept
Converting copy-assignment from a compatible (derived) handle.
const AllocatorType & GetAllocator() const noexcept
Returns a reference to the stored allocator.
bool operator==(const AtomicRefCounted< Other, Allocator > &other) const noexcept
Compares handle identity against a compatible derived handle.
AtomicRefCounted(Derived *ptr, std::pmr::memory_resource *resource) noexcept
Constructs from a raw pointer, taking ownership, with a PMR memory resource.
AtomicRefCounted(Derived *ptr) noexcept
Constructs from a raw pointer using a default-constructed allocator.
AtomicRefCounted(const AtomicRefCounted< Other, Allocator > &other) noexcept
Converting copy constructor from a compatible (derived) type.
AtomicRefCounted(const AtomicRefCounted &other) noexcept
Copy-constructs a handle sharing ownership with other.
constexpr AtomicRefCounted(std::pmr::memory_resource *resource) noexcept
Constructs a null handle with a PMR memory resource.
~AtomicRefCounted() noexcept
Destroys the handle and decrements the reference count.
AtomicRefCounted & operator=(const AtomicRefCounted &other) noexcept
Copy-assigns shared ownership from other.
constexpr AtomicRefCounted(std::nullptr_t, AllocatorType alloc) noexcept
Constructs a null handle explicitly with an explicit allocator.
AtomicRefCounted & operator=(AtomicRefCounted &&other) noexcept
Move-assigns ownership from other.
constexpr AtomicRefCounted() noexcept=default
Constructs a null handle.
AtomicRefCounted(Derived *ptr, AllocatorType alloc) noexcept
Constructs from a raw pointer, taking ownership, with an explicit allocator.
bool operator==(std::nullptr_t) const noexcept
Checks whether the handle is null.
bool Unique() const noexcept
Checks if this handle is the sole owner of the managed object.
AtomicRefCounted(AtomicRefCounted &&other) noexcept
Move-constructs a handle transferring ownership from other.
AtomicRefCounted(AtomicRefCounted< Other, Allocator > &&other) noexcept
Converting move constructor from a compatible (derived) type.
Derived * operator->() const noexcept
Accesses the managed object through pointer syntax.
AtomicRefCounted & operator=(AtomicRefCounted< Other, Allocator > &&other) noexcept
Converting move-assignment from a compatible (derived) handle.
Derived * Get() const noexcept
Returns the raw pointer without releasing ownership.
CRTP base class that embeds a non-atomic reference counter into the derived type.
RcFromThis & operator=(RcFromThis &&) noexcept
Move-assigns; does not affect the reference count.
uint32_t RefCount() const noexcept
Returns the current reference count.
RcFromThis(RcFromThis &&) noexcept
Move-constructs; does not transfer the reference count.
~RcFromThis() noexcept=default
Destroys the embedded counter; does not delete the derived object.
RcFromThis() noexcept=default
Default-constructs with reference count zero.
Non-atomic intrusive reference-counted smart pointer.
bool operator==(std::nullptr_t) const noexcept
Checks whether the handle is null.
void Reset() noexcept
Releases ownership and resets the handle to null.
RefCounted(const RefCounted< Other, Allocator > &other) noexcept
Converting copy constructor from a compatible (derived) type.
Derived * Get() const noexcept
Returns the raw pointer without releasing ownership.
RefCounted & operator=(RefCounted< Other, Allocator > &&other) noexcept
Converting move-assignment from a compatible (derived) handle.
~RefCounted() noexcept
Destroys the handle and decrements the reference count.
bool Empty() const noexcept
Checks if the handle is null.
constexpr RefCounted(std::pmr::memory_resource *resource) noexcept
Constructs a null handle with a PMR memory resource.
Derived & operator*() const noexcept
Dereferences the managed object.
constexpr RefCounted(std::nullptr_t, AllocatorType alloc) noexcept
Constructs a null handle explicitly with an explicit allocator.
RefCounted(Derived *ptr, AllocatorType alloc) noexcept
Constructs from a raw pointer, taking ownership, with an explicit allocator.
RefCounted(RefCounted &&other) noexcept
Move-constructs a handle transferring ownership from other.
RefCounted(Derived *ptr, std::pmr::memory_resource *resource) noexcept
Constructs from a raw pointer, taking ownership, with a PMR memory resource.
RefCounted & operator=(const RefCounted< Other, Allocator > &other) noexcept
Converting copy-assignment from a compatible (derived) handle.
Derived * operator->() const noexcept
Accesses the managed object through pointer syntax.
RefCounted & operator=(const RefCounted &other) noexcept
Copy-assigns shared ownership from other.
RefCounted(const RefCounted &other) noexcept
Copy-constructs a handle sharing ownership with other.
const AllocatorType & GetAllocator() const noexcept
Returns a reference to the stored allocator.
constexpr RefCounted() noexcept=default
Constructs a null handle.
bool Unique() const noexcept
Checks if this handle is the sole owner of the managed object.
bool operator==(const RefCounted< Other, Allocator > &other) const noexcept
Compares handle identity against a compatible derived handle.
uint32_t RefCount() const noexcept
Returns the current reference count.
RefCounted(RefCounted< Other, Allocator > &&other) noexcept
Converting move constructor from a compatible (derived) type.
RefCounted & operator=(std::nullptr_t) noexcept
Assigns null, releasing any currently managed object.
RefCounted & operator=(RefCounted &&other) noexcept
Move-assigns ownership from other.
constexpr RefCounted(std::nullptr_t) noexcept
Constructs a null handle explicitly (default-constructible allocators only).
bool operator==(const RefCounted &other) const noexcept
Compares handle identity by managed pointer address.
RefCounted(Derived *ptr) noexcept
Constructs from a raw pointer using a default-constructed allocator.
auto MakeArc(Args &&... args) -> Arc< T >
Allocates and constructs a T object, returning an Arc<T, std::allocator<T>> handle.
auto MakeRc(Args &&... args) -> Rc< T >
Allocates and constructs a T object, returning a Rc<T, std::allocator<T>> handle.
AtomicRefCounted< T, Allocator > Arc
Alias for AtomicRefCounted<T> — thread-safe intrusive reference-counted handle.
AtomicRefCounted< T, std::pmr::polymorphic_allocator< T > > PmrArc
Alias for AtomicRefCounted<T> using polymorphic memory allocator.
auto MakeArcWith(Allocator alloc, Args &&... args) -> Arc< T, Allocator >
Overload of MakeArc accepting a pre-constructed allocator instance.
auto MakeRcWith(Allocator alloc, Args &&... args) -> Rc< T, Allocator >
Overload of MakeRc accepting a pre-constructed allocator instance.
RefCounted< T, Allocator > Rc
Alias for RefCounted<T> — non-atomic intrusive reference-counted handle.
RefCounted< T, std::pmr::polymorphic_allocator< T > > PmrRc
Alias for RefCounted<T> using polymorphic memory allocator.
STL namespace.