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