ALib C++ Library
Library Version: 2412 R0
Documentation generated by doxygen
Loading...
Searching...
No Matches
sharedval.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_VAL
9#define HPP_ALIB_CONTAINERS_SHARED_VAL 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 is an alternative for C++ standard library type
23/// \https{std::shared_ptr,en.cppreference.com/w/cpp/memory/shared_ptr}
24/// with important restrictions:
25/// - Instead of managing a pointer, a \b value is managed.
26/// - It is not possible to store derived types within this type, which is a common use case with
27/// <c>std::shared_ptr</c>, especially in consideration with dynamic (virtual) types.<br>
28/// This implies that no abstract types can be stored.
29/// - This implementation misses the coexistence of sibling type <c>std::weak_ptr</c> and
30/// corresponding functionality.
31/// - This implementation misses an equivalent to method <c>owner_before</c> and corresponding
32/// comparison operators.
33/// - This implementation misses dedicated array support (at least as of today).
34///
35/// The advantages are:
36/// - The type has a footprint of only <c>sizeof(void*)</c>, where the standard's type has
37/// a size of two pointers.
38/// - An internal second pointer-dereferencing is avoided when accessing the shared value.
39/// - The type performs only one allocation.
40/// (Common implementations of the standard's type perform two allocations.)
41/// - The type supports storing references to \alib {lang;Allocator;allocators} which are
42/// used for allocation and freeing of memory.
43/// Allocators can be of "heavy" weight and are never copied by value.
44///
45/// Note that despite being named <b>Shared<em><u>Val</u></em></b>, which is in contrast to
46/// sibling type <b>Shared<em><u>Ptr</u></em></b>, the type still behaves like a pointer.
47/// It can be \e nulled, value <c>nullptr</c> can be assigned, and member access is performed
48/// with the <c>operator-></c>. And of-course the object is destructed, and the memory is freed in
49/// case the last copy of an instance is nulled or gets out of scope. A different naming
50/// proposal could have been <b>Shared<em><u>Static</u></em>Ptr</b> to indicate that no
51/// dynamic conversions and abstract types are applicable.
52///
53/// \see
54/// - Class \alib{containers;SharedPtr} which allows storing derived, dynamic types.
55/// - Class \alib{monomem;TSharedMonoVal} of module \alib_monomem, which incorporates
56/// an own embedded instance of class \alib{monomem;TMonoAllocator}.
57/// This allocator can be used for further monotonic allocations by the contained type or
58/// other code entities that receive the shared pointer.
59/// @tparam T The custom type that is shared with this pointer.
60/// @tparam TAllocator The allocator that is used to allocate an instance of \p{T} together
61/// with a reference counter and optionally a reference to such allocator
62/// if passed with construction.
63//==================================================================================================
64template<typename T, typename TAllocator= HeapAllocator>
66{
67 protected:
68 /// Fields if \p{TAllocator} is default constructible (e.g., \alib{lang;HeapAllocator}).
70 {
71 /// The instance of the custom type.
73
74 /// The reference counter used to implement the <c>std::shared_ptr</c> behavior.
75 std::atomic<unsigned int> refCount;
76
77 /// Alternative constructor missing the allocator instance.
78 /// This is used only with allocators that are default constructible
79 /// (like \alib{lang;HeapAllocator} is).
80 /// @tparam TArgs The argument types used for constructing \p{T}.
81 /// @param args The arguments for constructing \p{T}.
82 template<typename... TArgs,
83 typename TEnableIf= TAllocator, ATMP_IF(std::is_default_constructible<TEnableIf>::value)>
84 FieldMembersNoTA( TArgs&&... args )
85 : custom(std::forward<TArgs>(args)...)
86 , refCount(1) {}
87
88 ///@return A heap allocator value.
89 static constexpr TAllocator GetAllocator() noexcept { return lang::HeapAllocator(); }
90 }; // struct FieldMembers;
91
92 /// Fields if \p{TAllocator} is not default constructible (not \alib{lang;HeapAllocator}).
94 {
95 /// The instance of the custom type.
97
98 /// The reference counter used to implement the <c>std::shared_ptr</c> behavior.
99 std::atomic<unsigned int> refCount;
100
101 /// The allocator used.
102 TAllocator& allocator;
103
104 /// Constructor.
105 /// @param pAllocator The allocator, stored in parent \b AllocatorMember.
106 /// @tparam TArgs The argument types used for constructing \p{T}.
107 /// @param args The arguments for constructing \p{T}.
108 template<typename... TArgs>
109 FieldMembersWithAllocator( TAllocator& pAllocator, TArgs&&... args )
110 : custom(std::forward<TArgs>(args)...)
111 , refCount(1)
112 , allocator(pAllocator) {}
113
114 ///@return The stored allocator.
115 TAllocator& GetAllocator() const { return allocator; }
116
117 }; // struct FieldMembers;
118
119 /// The type of the stored data. Note that we cannot use helper \alib{lang;AllocatorMember},
120 /// because type T could be derived from the same base class and this would break
121 /// EBO rules, that force the compiler to give two distinguishable members, inherited or
122 /// not, a different address.
123 using FieldMembers = ATMP_IF_T_F( std::is_default_constructible<TAllocator>::value,
126
127 /// The stored data.
129
130 /// Internal shortcut to receive the custom member.
131 /// @return The pointer to the contained type, or \c nullptr in case this is empty.
132 T* getP() const noexcept
133 { return members ? &members->custom : nullptr; }
134
135 /// Internal shortcut to receive a reference to the custom member.
136 /// Asserts in debug-compilations.
137 /// @return A reference the contained type.
138 T& getR() const noexcept
139 {
140 ALIB_ASSERT_ERROR( members, "CONTAINERS", "Accessing nulled SharedVal." )
141 return members->custom;
142 }
143 public:
144 /// Exposes the allocator as given with template parameter \p{TAllocator}.
145 using AllocatorType= TAllocator;
146
147 /// Exposes the stored type specified with template parameter \p{T}.
148 using StoredType = T;
149
150 /// Default Constructor. Leaves this object \e nulled.
151 SharedVal() noexcept : members(nullptr) {}
152
153 /// Constructs an empty instance from \c std::nullptr.
154 /// This constructor is necessary to allow assignment of \c std::nullptr to values of this type,
155 /// which clears the automatic pointer.
156 SharedVal(std::nullptr_t) noexcept : members(nullptr) {}
157
158 /// Copy Constructor. Increases the reference counter of the shared pointer (in case given
159 /// \p{other} is not nulled).
160 /// @param other The object to copy.
161 SharedVal(const SharedVal& other) noexcept
162 : members(other.members) { if(members) ++(members->refCount); }
163
164
165 /// Move Constructor. Does not increase the reference counter, instead nulls the \p{other}.
166 /// @param other The object to copy.
167 SharedVal(SharedVal&& other) noexcept
168 : members(other.members) { other.members= nullptr; }
169
170 /// Copy Assignment Operator. Cares for self-assignment and assignment of a shared pointer with
171 /// the same content.
172 /// Otherwise, the reference counter of the current object is decreased, disposed if
173 /// necessary, and then the object in \p{other} is copied to this object.
174 /// @param other The object to copy into this one.
175 /// @return A reference to \c this.
176 SharedVal& operator=(const SharedVal& other) noexcept
177 {
178 // handle self assignment and assignment with same contents
179 if (this == &other || this->members == other.members)
180 return *this;
181
182 // decrement the old reference count and delete the old data if needed
183 if (members && members->refCount.fetch_sub(1) == 1)
184 members->GetAllocator()().Delete(members);
185
186 // copy the new data
187 if((members= other.members) != nullptr)
188 ++(members->refCount);
189
190 return *this;
191 }
192
193 /// Move Assignment Operator. Cares for self-assignment.
194 /// Otherwise, the object in \p{other} is copied to this.
195 /// @param other The object to move into this one.
196 /// @return A reference to \c this.
197 SharedVal& operator=(SharedVal&& other) noexcept
198 {
199 // handle self assignment
200 if (this == &other)
201 return *this;
202
203 // decrement the old reference count and delete the old data if needed
204 if (members && members != other.members && members->refCount.fetch_sub(1) == 1)
205 members->GetAllocator()().Delete(members);
206
207 // move members
208 members= other.members;
209 other.members= nullptr;
210 return *this;
211 }
212
213 #if DOXYGEN
214 /// Constructor taking an allocator along with the construction parameters for the instance
215 /// of \p{T}. The allocator is used allocate the needed memory (one allocation) and the
216 /// reference to it is internally stored, to be able to free the memory later.
217 ///
218 /// \note
219 /// This constructor is accepted by the compiler only if template type \p{TAllocator}
220 /// is \b not default constructible.
221 /// @param allocator The allocator used to allocate and free needed storage.
222 /// @tparam TArgs The argument types used for constructing \p{T}.
223 /// @param args The arguments for constructing \p{T}.
224 template<typename... TArgs>
225 SharedVal( TAllocator& allocator, TArgs&&... args );
226 #else
227 template< typename... TArgs,
228 typename TEnableIf= TAllocator,
229 ATMP_IF(!std::is_default_constructible<TEnableIf>::value) >
230 SharedVal( TAllocator& allocator, TArgs&&... args )
231 : members( allocator().template New<FieldMembers>( allocator,
232 std::forward<TArgs>(args)...) ) {}
233 #endif
234
235 #if DOXYGEN
236 /// Constructor missing the allocator instance.
237 /// To be used only with allocators that are default constructible
238 /// (like \alib{lang;HeapAllocator} is).
239 ///
240 /// \note
241 /// This constructor is accepted by the compiler only if
242 /// - \p{TAllocator} is default constructible, and
243 /// - the variadic argument list is not empty (here default construction is chosen), and
244 /// - the variadic arguments would not match to the copy or move constructor, and
245 /// - the variadic arguments are constructing type \p{T}.
246 ///
247 /// @tparam TArgs The argument types used for constructing \p{T}.
248 /// @param args The arguments for constructing \p{T}.
249 template<typename... TArgs>
250 SharedVal( TArgs&&... args );
251 #else
252 // If this was not done, this constructor was chosen for copy constructors of
253 // this type. Then the compilation of this constructor would fail when initializing
254 // field member below.
255 template <typename... TArgs, typename TEnableIf= TAllocator,
256 ATMP_IF( std::is_default_constructible_v<TEnableIf>
257 && !std::is_constructible_v< SharedVal ALIB_COMMA TArgs... >
258 && (sizeof...(TArgs) > 0)
259 && std::is_constructible_v<T ALIB_COMMA TArgs...>
260 ) >
261 SharedVal(TArgs&&... args)
262 : members( TAllocator()().template New<FieldMembers>(std::forward<TArgs>(args)...) ) {}
263 #endif
264
265 /// Destructor. If this is the last copy, the destructor of \p{T} is invoked and the
266 /// memory is freed to \p{TAllocator}.
268 {
269 if (members && members->refCount.fetch_sub(1) == 1)
270 members->GetAllocator()().Delete(members);
271 }
272
273 /// @return The size of the memory that is allocated for the \p{T} as well as for
274 /// the reference counter and the allocator member.
275 /// (To whom it may concern.)
276 static constexpr size_t SizeOfAllocation() { return sizeof(FieldMembers); }
277
278 /// @return The allocator given with construction that will be used to free the memory
279 /// that had been allocated, at the moment the use counter becomes \c 0.
281 {
282 ALIB_ASSERT_ERROR( members, "CONTAINERS", "Accessing nulled SharedVal." )
283 return members->GetAllocator();
284 }
285
286 /// Returns the number of shared usages.
287 /// In a multithreaded environment, the value returned is approximate.
288 /// @return \c The number of shared usages.
289 /// If this instance was default-constructed, moved, method #SetNulled was called,
290 /// or \c nullptr was assigned, then \c 0 is returned.
291 unsigned int UseCount() const noexcept
292 { return members != nullptr ? members->refCount.load() : 0; }
293
294 /// Returns \c true if the #UseCount is \c 1.
295 /// @return \c true if this instance is set but not shared.
296 bool Unique() const noexcept
297 { return members && members->refCount.load() == 1; }
298
299 /// Sets this object to \e nulled state, as if default constructed or \c nullptr was assigned.
300 /// If no shared copy exists, all data is destructed and memory is freed.<br>
301 /// As an alternative to this method, \c nullptr can be assigned.
302 void SetNulled() { lang::Destruct(*this); members= nullptr; }
303
304 /// Returns \c true if this is an empty instance.
305 /// @return \c true if #UseCount is \c 0, \c false otherwise.
306 bool IsNulled() const noexcept { return members == nullptr; }
307
308 /// Assignment of <c>nullptr</c>. Same as #SetNulled.
309 void operator=(std::nullptr_t) { lang::Destruct(*this); members= nullptr; }
310
311 /// Comparison with <c>nullptr</c>.
312 /// @return \c true if #UseCount is greater than \c 0, \c false otherwise.
313 bool operator==(std::nullptr_t) const noexcept { return members == nullptr; }
314
315 /// Comparison with <c>nullptr</c>.
316 /// @return \c false if #UseCount is greater than \c 0, \c false otherwise.
317 bool operator!=(std::nullptr_t) const noexcept { return members != nullptr; }
318
319 /// @return \c true if this instance is not \e nulled, \c false otherwise.
320 operator bool() const noexcept { return members != nullptr; }
321
322 /// Returns a non-constant pointer to the stored object of type \p{T}.
323 /// @return A pointer to \p{T}.
324 T* Get() const { return getP(); }
325
326 /// Overloaded operator to access members of custom type \p{T}
327 /// @return A pointer to \p{T}.
328 T* operator->() const { return getP(); }
329
330 /// Overloaded operator to access members of custom type \p{T}
331 /// @return A pointer to \p{T}.
332 T& operator*() const { return getR(); }
333}; // class SharedVal
334
335} // namespace alib[::containers]
336
337/// Type alias in namespace \b alib.
338template<typename T, typename TAllocator= lang::HeapAllocator>
340
341} // namespace [alib]
342
343#endif // HPP_ALIB_CONTAINERS_SHARED_VAL
344
AllocatorType & GetAllocator() const
TAllocator AllocatorType
Exposes the allocator as given with template parameter TAllocator.
SharedVal & operator=(SharedVal &&other) noexcept
bool Unique() const noexcept
ATMP_IF_T_F(std::is_default_constructible< TAllocator >::value, FieldMembersNoTA, FieldMembersWithAllocator) FieldMembers
SharedVal() noexcept
Default Constructor. Leaves this object nulled.
static constexpr size_t SizeOfAllocation()
T StoredType
Exposes the stored type specified with template parameter T.
void operator=(std::nullptr_t)
Assignment of nullptr. Same as SetNulled.
SharedVal(std::nullptr_t) noexcept
bool operator==(std::nullptr_t) const noexcept
bool IsNulled() const noexcept
SharedVal & operator=(const SharedVal &other) noexcept
unsigned int UseCount() const noexcept
FieldMembers * members
The stored data.
SharedVal(TAllocator &allocator, TArgs &&... args)
bool operator!=(std::nullptr_t) const noexcept
SharedVal(TArgs &&... args)
SharedVal(const SharedVal &other) noexcept
T & getR() const noexcept
T * getP() const noexcept
SharedVal(SharedVal &&other) noexcept
#define ATMP_IF_T_F( Cond, T, F)
Definition tmp.hpp:50
#define ATMP_IF(Cond)
Definition tmp.hpp:56
#define ALIB_ASSERT_ERROR(cond,...)
Definition alib.hpp:1271
static ALIB_FORCE_INLINE void Destruct(T &object)
Definition alib.cpp:69
Fields if TAllocator is default constructible (e.g., HeapAllocator).
Definition sharedval.hpp:70
std::atomic< unsigned int > refCount
The reference counter used to implement the std::shared_ptr behavior.
Definition sharedval.hpp:75
static constexpr TAllocator GetAllocator() noexcept
Definition sharedval.hpp:89
T custom
The instance of the custom type.
Definition sharedval.hpp:72
Fields if TAllocator is not default constructible (not HeapAllocator).
Definition sharedval.hpp:94
FieldMembersWithAllocator(TAllocator &pAllocator, TArgs &&... args)
std::atomic< unsigned int > refCount
The reference counter used to implement the std::shared_ptr behavior.
Definition sharedval.hpp:99