ALib C++ Framework
by
Library Version: 2605 R0
Documentation generated by doxygen
Loading...
Searching...
No Matches
sharedptr.hpp
Go to the documentation of this file.
1//==================================================================================================
2/// \file
3/// This header-file is part of module \alib_containers of the \aliblong.
4///
5/// Copyright 2013-2026 A-Worx GmbH, Germany.
6/// Published under #"mainpage_license".
7//==================================================================================================
8ALIB_EXPORT namespace alib { namespace containers {
9
10//==================================================================================================
11/// This templated class lifts restrictions imposed by (otherwise slightly more efficient)
12/// class #"SharedVal" and is an almost full-featured alternative for
13/// C++ standard library type
14/// \https{std::shared_ptr,en.cppreference.com/w/cpp/memory/shared_ptr}.
15///
16/// What is missing compared to <c>std::shared_ptr</c>:
17/// - Pointers to externally allocated objects cannot be assigned.
18/// The shared objects are always created with construction of this class or with the method
19/// #".InsertDerived(TArgs&& ...)" .
20/// - Special method #".InsertDerived(TAllocator&, TArgs&& ...)" is to be used, in case types
21/// derived from \p{T} are to be placed inside.
22/// - Overall, the interface is more explicit and seems less convenient.
23/// - This implementation misses the coexistence of sibling type <c>std::weak_ptr</c> and
24/// corresponding functionality.
25/// - This implementation misses an equivalent to method <c>owner_before</c> and corresponding
26/// comparison operators.
27/// - This implementation misses dedicated array support (at least as of today).
28///
29/// Advantages are:
30/// - The type has a footprint of only <c>sizeof(void*)</c>, where the standard's type has
31/// a size of two pointers.
32/// - The type performs only one allocation.
33/// (Common implementations of the standard's type perform two allocations.)
34/// - The type supports storing references to \alib {lang;Allocator;allocators} which are
35/// used for allocation and freeing of memory. (In this case, of course, the footprint
36/// increases to two times the <c>sizeof(void*)</c>.)
37///
38/// \see
39/// - Sibling class #"SharedVal", with is a restricted but slightly more efficient
40/// version of this class.
41/// - Class #"TSharedMonoVal" of module \alib_monomem, which incorporates
42/// an own embedded instance of class #"TMonoAllocator".
43/// This allocator can be used for further monotonic allocations by the contained type or
44/// other code entities that receive the shared pointer.
45/// @tparam T The custom type that is shared with this pointer.
46/// @tparam TAllocator The allocator that is used to allocate an instance of \p{T} together
47/// with a reference counter and optionally a reference to such allocator
48/// if passed with construction.
49//==================================================================================================
50template<typename T, typename TAllocator= HeapAllocator>
51class SharedPtr {
52 protected:
53 /// The combined struct of members which are from the \p{TAllocator} passed in the constructor.
54 /// Note, that in the case that #"HeapAllocator" is used parent class
55 /// #"%AllocatorMember" will be empty.
56 template<typename U>
57 struct FieldMembers : lang::AllocatorMember<TAllocator> {
58 /// The duly cast pointer to the custom type behind us.
60
61 /// The size of the allocated pair of these fields and the custom type.
62 size_t allocSize;
63
64 /// The reference counter used to implement the <c>std::shared_ptr</c> behavior.
65 std::atomic<unsigned> refCount;
66
67 /// The instance, either derived or T.
68 U u;
69
70 /// Constructor.
71 /// @param pAllocator The allocator, stored in parent #"%AllocatorMember".
72 /// @param pCustom The pointer to #".custom", rightfully cast.
73 /// @param pAllocSize The size of the allocated pair, containing this and the
74 /// potentially derived object \p{T}.
75 FieldMembers( TAllocator& pAllocator, T* pCustom, size_t pAllocSize)
76 : lang::AllocatorMember<TAllocator>( pAllocator )
77 , custom (pCustom)
78 , allocSize(pAllocSize)
79 , refCount (1) {}
80
81 /// Alternative constructor missing the allocator instance.
82 /// @param pAllocSize The size of the allocated pair, containing this and the
83 /// potentially derived object \p{T}.
84 /// @param pCustom The pointer to #".custom", rightfully cast.
85 FieldMembers(T* pCustom, size_t pAllocSize)
86 : custom (pCustom)
87 , allocSize(pAllocSize)
88 , refCount (1) {}
89 }; // struct FieldMembers;
90
91 /// The allocated stored data. Note that \p{T} can be a derived type.
92 FieldMembers<void*>* members;
93
94 /// Internal shortcut to receive the custom member.
95 /// @return The pointer to the contained type, or \c nullptr in case this is empty.
96 T* getP() const noexcept { return members ? reinterpret_cast<T*>(&members->u) : nullptr; }
97
98 /// Internal shortcut to receive a reference to the custom member.
99 /// Asserts in debug-compilations.
100 /// @return A reference the contained type.
101 T& getR() const noexcept {
102 ALIB_ASSERT_ERROR( members, "CONTAINERS", "Accessing nulled SharedVal." )
103 return reinterpret_cast<T&>(members->u);
104 }
105
106
107 public:
108 /// Exposes the allocator as given with template parameter \p{TAllocator}.
109 using AllocatorType= TAllocator;
110
111 /// Exposes the stored type specified with template parameter \p{T}.
112 using StoredType = T;
113
114 /// Default Constructor. Leaves this object \e nulled.
115 SharedPtr() noexcept : members(nullptr) {}
116
117 /// Constructs an empty instance from \c std::nullptr.
118 /// This constructor is necessary to allow assignment of \c std::nullptr to values of this type,
119 /// which clears the automatic pointer.
120 SharedPtr(std::nullptr_t) noexcept : members(nullptr) {}
121
122 /// Copy Constructor. Increases the reference counter of the shared pointer (in case given
123 /// \p{other} is not nulled).
124 /// @param other The object to copy.
125 SharedPtr(const SharedPtr& other) noexcept
126 : members(other.members) { if(members) ++(members->refCount); }
127
128
129 /// Move Constructor. Does not increase the reference counter, instead nulls the \p{other}.
130 /// @param other The object to copy.
131 SharedPtr(SharedPtr&& other) noexcept
132 : members(other.members) { other.members= nullptr; }
133
134 /// Copy Assignment Operator. Cares for self-assignment and assignment of a shared pointer with
135 /// the same content.
136 /// Otherwise, the reference counter of the current object is decreased, disposed if
137 /// necessary, and then the object in \p{other} is copied to this object.
138 /// @param other The object to copy into this one.
139 /// @return A reference to \c this.
140 SharedPtr& operator=(const SharedPtr& other) noexcept {
141 // handle self assignment and assignment with same contents
142 if (this == &other || this->members == other.members)
143 return *this;
144
145 // decrement the old reference count and delete the old data if needed
146 if (members && members->refCount.fetch_sub(1) == 1) {
148 members->GetAllocator().free( members, members->allocSize );
149 }
150
151 // copy the new data
152 if((members= other.members) != nullptr)
153 ++(members->refCount);
154
155 return *this;
156 }
157
158 /// Move Assignment Operator. Cares for self-assignment.
159 /// Otherwise, the object in \p{other} is copied to this.
160 /// @param other The object to move into this one.
161 /// @return A reference to \c this.
162 SharedPtr& operator=(SharedPtr&& other) noexcept {
163 // handle self assignment
164 if (this == &other)
165 return *this;
166
167 // decrement the old reference count and delete the old data if needed
168 if (members && members != other.members && members->refCount.fetch_sub(1) == 1) {
170 members->GetAllocator().free( members, members->allocSize );
171 }
172
173 // move members
174 members= other.members;
175 other.members= nullptr;
176 return *this;
177 }
178
179 /// Constructor taking an allocator along with the construction parameters for the instance
180 /// of \p{T}. The allocator is used allocate the needed memory (one allocation) and the
181 /// reference to it is internally stored, to be able to free the memory later.
182 ///
183 /// \note
184 /// This constructor is accepted by the compiler only if template type \p{TAllocator}
185 /// is \b not default-constructible.
186 /// @tparam TArgs The argument types used for constructing \p{T}.
187 /// @tparam TRequires Defaulted template parameter. Must not be specified.
188 /// @param allocator The allocator used to allocate and free needed storage.
189 /// @param args The arguments for constructing \p{T}.
190 template< typename... TArgs, typename TRequires= TAllocator>
191 requires ( !std::default_initializable<TRequires> )
192 SharedPtr( TAllocator& allocator, TArgs&&... args ) {
193 auto* mem= allocator().template Alloc<FieldMembers<T>>();
194 members = reinterpret_cast<FieldMembers<void*>*>(mem);
195 new (members) FieldMembers<void*>(allocator, &mem->u, sizeof(FieldMembers<T>));
196 new (&mem->u) T(std::forward<TArgs>(args)...);
197 }
198
199 /// Constructor missing the allocator instance.
200 /// To be used only with allocators that are default-constructible
201 /// (like #"HeapAllocator" is).
202 ///
203 /// \note
204 /// This constructor is accepted by the compiler only if
205 /// - \p{TAllocator} is default-constructible, and
206 /// - the variadic argument list is not empty (here default construction is chosen), and
207 /// - the variadic arguments would not match to the copy or move constructor, and
208 /// - the variadic arguments are constructing type \p{T}.
209 ///
210 /// @tparam TRequires Defaulted template parameter. Must not be specified.
211 /// @tparam TArgs The argument types used for constructing \p{T}.
212 /// @param args The arguments for constructing \p{T}.
213 template <typename... TArgs, typename TRequires= TAllocator>
214 requires( std::is_default_constructible_v<TRequires>
215 && !std::is_same_v<std::decay_t<TArgs>... , SharedPtr>
216 && (sizeof...(TArgs) > 0) )
217 SharedPtr(TArgs&&... args) {
218 auto* mem= TAllocator()().template Alloc<FieldMembers<T>>();
219 members = reinterpret_cast<FieldMembers<void*>*>(mem);
220 new (members) FieldMembers<void*>(&mem->u, sizeof(FieldMembers<T>));
221 new (&mem->u) T(std::forward<TArgs>(args)...);
222 }
223
224 /// Disposes any currently held data (in case this was the last user, the current object
225 /// is deleted and memory freed) and places a new instance of (potentially) derived
226 /// type \p{TDerived} in this object.
227 ///
228 /// This overload of the method is accepted by the compiler only if type \p{TAllocator} is
229 /// default-constructible.
230 ///
231 /// @tparam TRequires Defaulted template parameter. Must not be specified.
232 /// @tparam TArgs The argument types used for constructing \p{T}.
233 /// @param args The arguments for constructing \p{T}.
234 template <typename TDerived, typename TRequires= TAllocator, typename... TArgs>
235 requires std::is_default_constructible_v<TRequires>
236 void InsertDerived(TArgs&&... args) {
237 // delete any existing
238 lang::Destruct(*this);
239
240 auto* mem= TAllocator()().template Alloc<FieldMembers<TDerived>>();
241 members = reinterpret_cast<FieldMembers<void*>*>(mem);
242 new (members) FieldMembers<void*>( lang::SafeCast<T>(&mem->u),
243 sizeof(FieldMembers<TDerived>) );
244 new (&mem->u) TDerived(std::forward<TArgs>(args)...);
245 }
246
247 /// Disposes any currently held data (in case this was the last user, the current object
248 /// is deleted and memory freed) and places a new instance of (potentially) derived
249 /// type \p{TDerived} in this object.
250 ///
251 /// This overload of the method is accepted by the compiler only if type \p{TAllocator} is
252 /// not default-constructible.
253 ///
254 /// @tparam TRequires Defaulted template parameter. Must not be specified.
255 /// @tparam TArgs The argument types used for constructing \p{T}.
256 /// @param allocator The allocator to use.
257 /// @param args The arguments for constructing \p{T}.
258 template <typename TDerived, typename TRequires= TAllocator, typename... TArgs>
259 requires( !std::is_default_constructible_v<TRequires> )
260 void InsertDerived(TAllocator& allocator, TArgs&&... args) {
261 // delete any existing
262 lang::Destruct(*this);
263 new (this) SharedPtr(); // note: without this, some compilers complain about
264 // uninitialized member 'members'!
265
266 auto* mem= allocator().template Alloc<FieldMembers<TDerived>>();
267 members = reinterpret_cast<FieldMembers<void*>*>(mem);
268 new (members) FieldMembers<void*>( allocator,
269 lang::SafeCast<T>(&mem->u),
270 sizeof(FieldMembers<TDerived>) );
271 new (&mem->u) TDerived(std::forward<TArgs>(args)...);
272 }
273
274 /// Destructor. If this is the last copy, the destructor of \p{T} is invoked and the
275 /// memory is freed to \p{TAllocator}.
277 if (members && members->refCount.fetch_sub(1) == 1) {
279 members->GetAllocator().free( members, members->allocSize );
280 } }
281
282 /// @return The size of the memory that is allocated for the \p{T} as well as for
283 /// the reference counter and the allocator member.
284 /// (To whom it may concern.)
285 template<typename TStored= T>
286 static constexpr size_t SizeOfAllocation() noexcept { return sizeof(FieldMembers<TStored>); }
287
288 /// @return The allocator given with construction that will be used to free the memory
289 /// that had been allocated, at the moment the use counter becomes \c 0.
291 ALIB_ASSERT_ERROR( members, "CONTAINERS", "Accessing nulled SharedVal." )
292 return members->GetAllocator();
293 }
294
295 /// @return The allocator interface of the allocator received with construction.
297
298 /// Returns the number of shared usages.
299 /// In a multithreaded environment, the value returned is approximate.
300 /// @return \c The number of shared usages.
301 /// If this instance was default-constructed, moved, method #".SetNulled" was called,
302 /// or \c nullptr was assigned, then \c 0 is returned.
303 unsigned UseCount() const noexcept
304 { return members != nullptr ? members->refCount.load() : 0; }
305
306 /// Returns \c true if the #".UseCount" is \c 1.
307 /// @return \c true if this instance is set but not shared.
308 bool Unique() const noexcept { return members && members->refCount.load() == 1; }
309
310 /// Sets this object to \e nulled state, as if default constructed or \c nullptr was assigned.
311 /// If no shared copy exists, all data is destructed and memory is freed.<br>
312 /// As an alternative to this method, \c nullptr can be assigned.
313 void SetNulled() { lang::Destruct(*this); members= nullptr; }
314
315 /// Returns \c true if this is an empty instance.
316 /// @return \c true if #".UseCount" is \c 0, \c false otherwise.
317 bool IsNulled() const noexcept { return members == nullptr; }
318
319 /// Assignment of <c>nullptr</c>. Same as #".SetNulled".
320 void operator=(std::nullptr_t) { lang::Destruct(*this); members= nullptr; }
321
322 /// Comparison with <c>nullptr</c>.
323 /// @return \c true if #".UseCount" is greater than \c 0.
324 bool operator==(std::nullptr_t) const noexcept { return members == nullptr; }
325
326 /// Comparison with <c>nullptr</c>.
327 /// @return \c false if #".UseCount" is greater than \c 0.
328 bool operator!=(std::nullptr_t) const noexcept { return members != nullptr; }
329
330 /// @return \c true if this instance is not \e nulled, \c false otherwise.
331 operator bool() const noexcept { return members != nullptr; }
332
333 /// Returns a non-constant pointer to the stored object of type \p{T}.
334 /// @return A pointer to \p{T}.
335 T* Get() const { return getP(); }
336
337 /// Overloaded operator to access members of custom type \p{T}
338 /// @return A pointer to \p{T}.
339 T* operator->() const { return getP(); }
340
341 /// Overloaded operator to access members of custom type \p{T}
342 /// @return A pointer to \p{T}.
343 T& operator*() const { return getR(); }
344
345}; // class SharedPtr
346
347} // namespace alib[::containers]
348
349/// Type alias in namespace #"%alib".
350template<typename T, typename TAllocator= lang::HeapAllocator>
352
353} // namespace [alib]
#define ALIB_EXPORT
#define ALIB_ASSERT_ERROR(cond, domain,...)
T StoredType
Exposes the stored type specified with template parameter T.
bool operator==(std::nullptr_t) const noexcept
static constexpr size_t SizeOfAllocation() noexcept
SharedPtr(const SharedPtr &other) noexcept
TAllocator AllocatorType
Exposes the allocator as given with template parameter TAllocator.
SharedPtr & operator=(SharedPtr &&other) noexcept
SharedPtr & operator=(const SharedPtr &other) noexcept
void InsertDerived(TAllocator &allocator, TArgs &&... args)
bool operator!=(std::nullptr_t) const noexcept
SharedPtr() noexcept
Default Constructor. Leaves this object nulled.
lang::AllocatorInterface< TAllocator > AIF() const noexcept
SharedPtr(SharedPtr &&other) noexcept
SharedPtr(TAllocator &allocator, TArgs &&... args)
void Destruct(T &object)
Definition tmp.hpp:82
ALIB_POP_ALLOWANCE TTo * SafeCast(TFrom *derived)
Definition tmp.hpp:31
Definition alox.cpp:14
containers::SharedPtr< T, TAllocator > SharedPtr
Type alias in namespace #"%alib".
FieldMembers(TAllocator &pAllocator, T *pCustom, size_t pAllocSize)
Definition sharedptr.hpp:75
size_t allocSize
The size of the allocated pair of these fields and the custom type.
Definition sharedptr.hpp:62
std::atomic< unsigned > refCount
The reference counter used to implement the std::shared_ptr behavior.
Definition sharedptr.hpp:65
T * custom
The duly cast pointer to the custom type behind us.
Definition sharedptr.hpp:59
FieldMembers(T *pCustom, size_t pAllocSize)
Definition sharedptr.hpp:85
U u
The instance, either derived or T.
Definition sharedptr.hpp:68