MCRO
C++23 utilities for Unreal Engine.
All Data Structures Namespaces Files Functions Variables Typedefs Enumerations Enumerator Friends Macros Pages Concepts
SharedObjects.h
Go to the documentation of this file.
1/** @noop License Comment
2 * @file
3 * @copyright
4 * This Source Code is subject to the terms of the Mozilla Public License, v2.0.
5 * If a copy of the MPL was not distributed with this file You can obtain one at
6 * https://mozilla.org/MPL/2.0/
7 *
8 * @author David Mórász
9 * @date 2025
10 */
11
12#pragma once
13
14#include "CoreMinimal.h"
15#include "Mcro/FunctionTraits.h"
16#include "Mcro/Construct.h"
17
18/** @brief Utilities for TSharedPtr/Ref and related */
20{
21 using namespace Mcro::Concepts;
22 using namespace Mcro::FunctionTraits;
23
24 /** @brief Copy thread-safety from other shared object type */
25 template <CSharedOrWeak T>
26 using TSharedPtrFrom = TSharedPtr<typename T::ElementType, T::Mode>;
27
28 /** @brief Copy thread-safety from other shared object type */
29 template <CSharedOrWeak T>
30 using TSharedRefFrom = TSharedRef<typename T::ElementType, T::Mode>;
31
32 /** @brief Copy thread-safety from other shared object type */
33 template <CSharedOrWeak T>
34 using TWeakPtrFrom = TWeakPtr<typename T::ElementType, T::Mode>;
35
36 /**
37 * @brief Concept describing an object which provides a deferred initializer for shared objects.
38 *
39 * This works around the annoyance of TSharedFromThis objects cannot use their shared pointers in their
40 * constructor, braking RAII in some cases. Of course this is only achievable if the object cooperates and
41 * implements an Initialize method.
42 * It's important that `Initialize` should not be virtual so derived classes can hide them with their own
43 * overload.
44 */
45 template <typename T, typename... Args>
46 concept CSharedInitializeable = requires(T& object, Args&&... args)
47 {
48 object.Initialize(Forward<Args>(args)...);
49 };
50
51 /**
52 * @brief
53 * A wrapper around MakeShareable that automatically calls an initializer method Initialize on the
54 * instantiated object.
55 *
56 * This works around the annoyance of TSharedFromThis objects cannot use their shared pointers in their
57 * constructor, braking RAII in some cases. Of course this is only achievable if the object cooperates and
58 * implements an Initialize method.
59 * It's important that Initialize should not be virtual so derived classes can hide them with their own
60 * overload.
61 */
62 template <typename T, ESPMode Mode = ESPMode::ThreadSafe, typename... Args>
63 requires CSharedInitializeable<T, Args...>
64 TSharedRef<T, Mode> MakeShareableInit(T* newObject, Args&&... args)
65 {
66 TSharedRef<T, Mode> result = MakeShareable(newObject);
67 result->Initialize(Forward<Args>(args)...);
68 return result;
69 }
70
71 /**
72 * @brief A combination of MakeShareable and Mcro::Construct::ConstructNew
73 *
74 * Usage:
75 * @code
76 * using namespace Mcro::SharedObjects;
77 *
78 * auto myObject = ConstructShared([](MyObject& _)
79 * {
80 * _.Foo = 42;
81 * _.Initialize();
82 * // etc...
83 * });
84 * static_assert(std::is_same_v<decltype(myObject), TSharedRef<MyObject>>);
85 * @endcode
86 *
87 * @param init A lambda function with a single l-value reference parameter of the object type to initialize.
88 * @param args Arguments of the object constructor
89 * @return A pointer to the object instance on heap.
90 * @remarks The C++ 20 designated initializers with named arguments has annoying limitations, therefore this exists
91 */
92 template <
93 CFunctorObject Initializer,
94 typename... Args,
95 ESPMode Mode = ESPMode::ThreadSafe,
96 typename ResultArg = TFunction_Arg<Initializer, 0>,
97 typename Result = std::decay_t<ResultArg>
98 >
99 requires std::is_lvalue_reference_v<ResultArg>
100 TSharedRef<Result, Mode> ConstructShared(Initializer&& init, Args&&... args)
101 {
102 using namespace Mcro::Construct;
103 return MakeShareable(
104 ConstructNew(Forward<Initializer>(init), Forward<Args>(args)...)
105 );
106 }
107
108 /**
109 * @brief Create a shared pointer which takes in an object with in-place refcounting.
110 *
111 * Refcounted TSharedPtr/Ref doesn't take ownership of the object and when the last reference goes out of scope it
112 * simply decreases the refcount instead of deleting the object.
113 */
114 template <CRefCounted T, ESPMode Mode = ESPMode::ThreadSafe>
115 TSharedRef<T, Mode> ShareRefCounted(T* object)
116 {
117 check(object);
118 object->AddRef();
119 return MakeShareable(object, [](T* object) { object->Release(); });
120 }
121
122 /**
123 * @brief
124 * Same as `SharedThis(this)` in `TSharedFromThis` but returning a weak pointer instead.
125 *
126 * Indeed, `TSharedFromThis` already has `AsWeak()` but that can only return the type which was originally set by
127 * `TSharedFromThis` making its usage slightly less convenient in derived classes.
128 */
129 template <
130 CSharedFromThis T,
131 ESPMode Mode = decltype(DeclVal<T const>().AsShared())::Mode
132 >
133 auto WeakSelf(const T* self) -> TWeakPtr<T const, Mode>
134 {
135 return StaticCastSharedRef<T const>(self->AsShared());
136 }
137
138 /**
139 * @brief
140 * Same as `SharedThis(this)` in `TSharedFromThis` but returning a weak pointer instead.
141 *
142 * Indeed, `TSharedFromThis` already has `AsWeak()` but that can only return the type which was originally set by
143 * `TSharedFromThis` making its usage slightly less convenient in derived classes.
144 */
145 template <
146 CSharedFromThis T,
147 ESPMode Mode = decltype(DeclVal<T>().AsShared())::Mode
148 >
149 auto WeakSelf(T* self) -> TWeakPtr<T, Mode>
150 {
151 return StaticCastSharedRef<T>(self->AsShared());
152 }
153
154 /**
155 * @brief
156 * A simple mutable shared storage of any value.
157 *
158 * One particular use case for this may be delegates which may remove themselves once invoked. For example:
159 * @code
160 * auto once = MakeSharedDelegateHandle();
161 * once->Value = MyEvent.AddLambda([this, once]
162 * {
163 * // execute something only once.
164 * MyEvent.Remove(once->Value);
165 * });
166 * @endcode
167 */
168 template <CDefaultInitializable T>
170 {
172 };
173
174 template <CDefaultInitializable T>
175 using TSharedStoragePtr = TSharedPtr<TSharedStorage<T>>;
176
177 template <CDefaultInitializable T>
178 using TSharedStorageRef = TSharedRef<TSharedStorage<T>>;
179
180 template <CDefaultInitializable T>
181 using TSharedStorageWeakPtr = TWeakPtr<TSharedStorage<T>>;
182
183 /** @brief A simple convenience wrapper around making FDelegateHandles shared */
185 {
186 return MakeShared<TSharedStorage<FDelegateHandle>>();
187 }
188}
Concept describing an object which provides a deferred initializer for shared objects.
typename TFunctionTraits< std::decay_t< T > >::template Arg< I > TFunction_Arg
Shorthand for getting a type of a function argument at given position I.
Utilities for TSharedPtr/Ref and related.
TSharedRef< T, Mode > MakeShareableInit(T *newObject, Args &&... args)
A wrapper around MakeShareable that automatically calls an initializer method Initialize on the insta...
TSharedRef< typename T::ElementType, T::Mode > TSharedRefFrom
Copy thread-safety from other shared object type.
TSharedPtr< typename T::ElementType, T::Mode > TSharedPtrFrom
Copy thread-safety from other shared object type.
TSharedRef< TSharedStorage< T > > TSharedStorageRef
TWeakPtr< typename T::ElementType, T::Mode > TWeakPtrFrom
Copy thread-safety from other shared object type.
FORCEINLINE TSharedStorageRef< FDelegateHandle > MakeSharedDelegateHandle()
A simple convenience wrapper around making FDelegateHandles shared.
TSharedRef< T, Mode > ShareRefCounted(T *object)
Create a shared pointer which takes in an object with in-place refcounting.
auto WeakSelf(const T *self) -> TWeakPtr< T const, Mode >
Same as SharedThis(this) in TSharedFromThis but returning a weak pointer instead.
TWeakPtr< TSharedStorage< T > > TSharedStorageWeakPtr
TSharedRef< Result, Mode > ConstructShared(Initializer &&init, Args &&... args)
A combination of MakeShareable and Mcro::Construct::ConstructNew.
TSharedPtr< TSharedStorage< T > > TSharedStoragePtr
A simple mutable shared storage of any value.