Stroika Library 3.0d16
 
Loading...
Searching...
No Matches
SharedByValue.h
Go to the documentation of this file.
1/*
2 * Copyright(c) Sophist Solutions, Inc. 1990-2025. All rights reserved
3 */
4#ifndef _Stroika_Foundation_Memory_SharedByValue_h_
5#define _Stroika_Foundation_Memory_SharedByValue_h_ 1
6
7#include "Stroika/Foundation/StroikaPreComp.h"
8
9#include <memory>
10
11#include "Stroika/Foundation/Common/Common.h"
12
13/**
14 * \file
15 *
16 * \note Code-Status: <a href="Code-Status.md#Beta">Beta</a>
17 *
18 * TODO:
19 * @todo Probably should use Debug::AssertExternallySyncrhonized in SharedByValue
20 *
21 */
22
23namespace Stroika::Foundation::Memory {
24
25 // @todo redo as C++ concept in C++20
26 template <typename T, typename SHARED_IMLP, typename COPIER>
27 constexpr bool SharedByValue_IsCopier ()
28 {
29 // @todo should match API function<SHARED_IMLP(const T&)>
30 return true;
31 }
32
33 /**
34 * \brief SharedByValue_CopyByDefault is the default template parameter for copying SharedByValue
35 *
36 * SharedByValue_CopyByDefault is the a simple copying mechanism used by SharedByValue<>.
37 * It simply hardwires use of new T() - the default T(T&) constructor to copy elements of type T.
38 */
39 template <typename T, typename SHARED_IMLP = shared_ptr<T>>
41 nonvirtual SHARED_IMLP operator() (const T& t) const;
42 };
43
44 /**
45 * \brief SharedByValue_Traits is a utility struct to provide parameterized support
46 * for SharedByValue<>
47 *
48 * This class should allow SHARED_IMLP to be std::shared_ptr (or another shared_ptr implementation).
49 */
50 template <typename T, typename SHARED_IMLP = shared_ptr<T>, typename COPIER = SharedByValue_CopyByDefault<T, SHARED_IMLP>>
52 using element_type = T;
53
54 /**
55 * Note that the COPIER can ASSERT externally synchronized, and doesnt need to synchronize itself.
56 */
57 using element_copier_type = COPIER;
58 using shared_ptr_type = SHARED_IMLP;
59 };
60
61 /**
62 * This state is meant purely for code that may manage their internal behavior
63 * based on details of reference counting - not for semantic reasons, but to enhance performance.
64 */
66 eNull,
67 eSolo,
68 eShared,
69 };
70
71 /**
72 * \brief SharedByValue is a utility class to implement Copy-On-Write (aka COW) - sort of halfway between unique_ptr and shared_ptr
73 *
74 * SharedByValue is a utility class to implement Copy-On-Write (aka Copy on Write, or COW).
75 *
76 * This utility class should not be used lightly. Its somewhat tricky to use properly. Its meant
77 * to facilitate implementing the copy-on-write semantics which are often handy in providing
78 * high-performance data structures.
79 *
80 * This class should allow SHARED_IMLP to be std::shared_ptr (or another shared_ptr implementation).
81 *
82 * This class template was originally called CopyOnWrite.
83 *
84 * \note Though there IS a fCopier, this is only the default copier, and calls to rwget() can always provide
85 * an alternative copier.
86 *
87 * \par Example Usage
88 * \code
89 * SharedByValue<vector<byte>> b{BLOB::Hex ("abcd1245").Repeat (100).As<vector<byte>> ()};
90 * SharedByValue<vector<byte>> c = b; // copied by reference until 'c' or 'b' changed values
91 * EXPECT_TRUE (c == b);
92 * \endcode
93 *
94 * \note \em Thread-Safety <a href="Thread-Safety.md#C++-Standard-Thread-Safety">C++-Standard-Thread-Safety</a>
95 *
96 * Understand that this works because SharedByValue objects are really shared_ptr, but with copy by value semantics.
97 * C++-Standard-Thread-Safety means that the envelope is always safe because its just following standard c++
98 * rules for copying the shared_ptr.
99 *
100 * And copying the indirected shared_ptr is always safe because the ONLY time anyone can ever MODIFY
101 * an object is if the shared_count == 1 (so no other threads using it).
102 *
103 * \note Design choice: embed fCopier into instance
104 * vs. just constructing the object on the fly the way we do for comparison functions like std::less<T> {} etc.
105 *
106 * PRO embed: If constructor cost for COPIER non-trivial, best todo once. If size can be zero, doesn't really
107 * matter/cost anything ([[no_unique_address]]). If you want to have some data in copier, and have that specific to
108 * the instance (I can think of no use case for this) - very tricky unless embedded.
109 *
110 * PRO NOT EMBED: Simpler todo access functions (default parameter instead of overload passing fCopier).
111 * For now - go with more flexible approach since not much more complex to implement.
112 *
113 * \note <a href="Design-Overview.md#Comparisons">Comparisons</a>:
114 * o Only comparison (operator==/!=) with nullptr is supported.
115 *
116 * Earlier versions of Stroika (before 2.1a5) supported operator==(SharedByValue) - and this kind of makes sense
117 * but is a little ambiguous if its measuring pointer (shared reference) equality or actual value equality.
118 *
119 * Better to let the caller use operator<=> on cget() or *cget() to make clear their intentions.
120 *
121 * TODO:
122 * @todo http://stroika-bugs.sophists.com/browse/STK-798 - review docs and thread safety
123 */
124 template <typename T, typename TRAITS = SharedByValue_Traits<T>>
126 public:
127 using element_type = typename TRAITS::element_type;
128 using element_copier_type = typename TRAITS::element_copier_type;
129 using shared_ptr_type = typename TRAITS::shared_ptr_type;
130
131 public:
132 static_assert (same_as<T, typename TRAITS::element_type>);
133
134 public:
135 /**
136 * SharedByValue::SharedByValue():
137 * The constructor takes either no/args to nullptr, to construct an empty SharedByValue.
138 *
139 * It can be copied by another copy of the same kind (including same kind of copier).
140 *
141 * Or it can be explicitly constructed from a SHARED_IMPL (any existing shared_ptr, along
142 * with a copier (defaults to SharedByValue_CopyByDefault). If passed a bare pointer, that
143 * pointer will be wrapped in a shared_ptr (so it better not be already), and the SharedByValue()
144 * will take ownership of the lifetime of that pointer.
145 *
146 * You can also copy a straight 'element_type' value into a SharedByValue.
147 */
148 SharedByValue () noexcept = default;
149 SharedByValue (nullptr_t n) noexcept;
150 SharedByValue (SharedByValue&& from) noexcept = default;
151 SharedByValue (const SharedByValue& from) noexcept = default;
152 explicit SharedByValue (const element_type& from, const element_copier_type& copier = element_copier_type{}) noexcept;
153 explicit SharedByValue (const shared_ptr_type& from, const element_copier_type& copier = element_copier_type{}) noexcept;
154 explicit SharedByValue (shared_ptr_type&& from, const element_copier_type&& copier = element_copier_type{}) noexcept;
155 explicit SharedByValue (element_type* from, const element_copier_type& copier = element_copier_type{});
156
157 public:
158 nonvirtual SharedByValue& operator= (SharedByValue&& src) noexcept = default;
159 nonvirtual SharedByValue& operator= (const SharedByValue& src) noexcept = default;
160 nonvirtual SharedByValue& operator= (shared_ptr_type&& from) noexcept;
161 nonvirtual SharedByValue& operator= (const shared_ptr_type& from) noexcept;
162
163 public:
164 /**
165 */
166 nonvirtual explicit operator bool () const noexcept;
167
168 public:
169 /**
170 * \brief access te underlying shared_ptr stored in the SharedByValue. This should be treated as readonly and
171 * only used to make calls that don't change / mutate the underlying object.
172 *
173 * \todo @todo Consider if using const somehow can help make this safer - returning a shared_ptr<const T>?? somehow
174 */
175 nonvirtual shared_ptr_type cget_ptr () const;
176
177 public:
178 /**
179 * \brief forced copy of the underlying shared_ptr data
180 *
181 * \note In Stroika v2.1, this was broken.
182 */
183 nonvirtual shared_ptr_type rwget_ptr ();
184 template <typename COPIER>
185 nonvirtual shared_ptr_type rwget_ptr (COPIER&& copier);
186
187 public:
188 /**
189 * rwget () returns the real underlying (modifiable) ptr we store. It can be nullptr.
190 *
191 * Importantly, it makes sure that there is at most one reference to the 'shared_ptr' value
192 * before returning that pointer, so the caller is the only one modifying the object.
193 *
194 * The no-arg overload uses the builtin copier (overwhelmingly most common), but occasionally its helpful
195 * to specify an alternate copier (see CONTAINER::_GetWritableRepAndPatchAssociatedIterator for example).
196 */
197 nonvirtual element_type* rwget ();
198 template <typename COPIER>
199 nonvirtual element_type* rwget (COPIER&& copier);
200
201 public:
202 /**
203 * cget returns returns the real underlying const ptr we store.
204 *
205 * \em Note: cget () will never invoke BreakReferences/Clone.
206 *
207 * To get a non-const pointer, @see rwget ()
208 */
209 nonvirtual const element_type* cget () const noexcept;
210
211 public:
212 /**
213 * These operators require that the underlying ptr is non-nil.
214 *
215 * \em note - the non-const overloads of operator-> and operator* only work if you use a COPY function
216 * that takes no arguments (otherwise there are no arguments to pass to the clone/copy function).
217 *
218 * You can always safely use the copy overload.
219 *
220 * \note This can be confusing, because at the point of call, its unclear if this may invoke BreakReferences or not
221 */
222 nonvirtual const element_type* operator->() const;
223 nonvirtual element_type* operator->();
224
225 public:
226 /**
227 * These operators require that the underlying ptr is non-nil.
228 */
229 nonvirtual const element_type& operator* () const;
230
231 public:
232 /**
233 */
234 constexpr bool operator== (nullptr_t) const;
235
236 public:
237 /**
238 */
239 nonvirtual element_copier_type GetDefaultCopier () const;
240
241 public:
242 /**
243 * @see SharedByValue_State.
244 *
245 * Note that two subsequent calls on an object CAN return different answers, without any calls to 'this' object.
246 * That's because another shared copy can lose a reference. So - if this once returns 'shared', it might later return
247 * solo, without any change to THIS object.
248 */
249 nonvirtual SharedByValue_State GetSharingState () const;
250
251 public:
252 /**
253 * Returns true if there is exactly one object referenced. Note that if empty () - then not unique().
254 */
255 nonvirtual bool unique () const;
256
257 public:
258 /**
259 * Returns the number of references to the underlying shared pointer.
260 *
261 * @see SharedByValue_State
262 */
263 nonvirtual unsigned int use_count () const;
264
265 private:
266 [[no_unique_address]] element_copier_type fCopier_; // often zero sized
267 shared_ptr_type fSharedImpl_;
268
269 public:
270 /**
271 * Assure there are at most N (typically one or 2) references to this object, and if there are more, break references.
272 * This method should be applied before destructive operations are applied to the shared object.
273 *
274 * Argument copier is typically fCopier_
275 *
276 * \note - QUEER - copies generated by BreakReferences_() use the original fCopier_, not the argument copier.
277 */
278 template <typename COPIER>
279 nonvirtual void AssureNOrFewerReferences (COPIER&& copier, unsigned int n = 1u);
280 nonvirtual void AssureNOrFewerReferences (unsigned int n = 1u);
281
282 private:
283 template <typename COPIER>
284 nonvirtual void BreakReferences_ (COPIER&& copier);
285 };
286
287}
288
289/*
290 ********************************************************************************
291 ***************************** Implementation Details ***************************
292 ********************************************************************************
293 */
294#include "SharedByValue.inl"
295
296#endif /*_Stroika_Foundation_Memory_SharedByValue_h_*/
SharedByValue is a utility class to implement Copy-On-Write (aka COW) - sort of halfway between uniqu...
nonvirtual SharedByValue_State GetSharingState() const
nonvirtual unsigned int use_count() const
nonvirtual shared_ptr_type cget_ptr() const
access te underlying shared_ptr stored in the SharedByValue. This should be treated as readonly and o...
nonvirtual shared_ptr_type rwget_ptr()
forced copy of the underlying shared_ptr data
nonvirtual const element_type * cget() const noexcept
nonvirtual void AssureNOrFewerReferences(COPIER &&copier, unsigned int n=1u)
SharedByValue_CopyByDefault is the default template parameter for copying SharedByValue.
SharedByValue_Traits is a utility struct to provide parameterized support for SharedByValue<>