Stroika Library 3.0d16
 
Loading...
Searching...
No Matches
forward_list.inl
1/*
2 * Copyright(c) Sophist Solutions, Inc. 1990-2025. All rights reserved
3 */
4#include <list>
5#include <memory>
6#include <vector>
7
10
11namespace Stroika::Foundation::Containers::LockFreeDataStructures {
12
13 /**
14 * construction is lock free (though begin() is not)
15 * incrementing is NOT lock free
16 */
17 template <typename T>
18 template <typename U>
19 class forward_list<T>::ForwardIterator_ {
20 friend class forward_list;
21 node_* current;
22 using iterator_category = std::forward_iterator_tag;
23 using value_type = U;
24 using reference = U&;
25 using pointer = U*;
26 //using difference_type = ptrdiff_t; // doesn't work cuz we dont support operator- on iterators (not sure what it should do?)
27
28 public:
29 ForwardIterator_ ()
30 : current{kTerminalSentinel_}
31 {
32 }
33 ForwardIterator_ (node_* n)
34 : current{n != kTerminalSentinel_ ? increment_reference_count_ (n) : kTerminalSentinel_}
35 {
36 }
37 ForwardIterator_ (const ForwardIterator_& other)
38 : current{other.current != kTerminalSentinel_ ? increment_reference_count_ (other.current) : kTerminalSentinel_}
39 {
40 }
41 ForwardIterator_ (ForwardIterator_&& other) noexcept
42 : current{kTerminalSentinel_}
43 {
44 std::swap (current, other.current);
45 }
46 ~ForwardIterator_ ()
47 {
48 if (current == kTerminalSentinel_) {
49 return;
50 }
51 decrement_reference_count_ (current);
52 }
53 ForwardIterator_& operator= (ForwardIterator_ const& other)
54 {
55 if (current != kTerminalSentinel_) {
56 decrement_reference_count_ (current);
57 }
58 current = other.current != kTerminalSentinel_ ? increment_reference_count_ (other.current) : kTerminalSentinel_;
59 return *this;
60 }
61 template <typename V>
62 ForwardIterator_& operator= (const V& rhs)
63 {
64 if (current != kTerminalSentinel_) {
65 decrement_reference_count_ (current);
66 }
67 current = rhs.current != kTerminalSentinel_ ? increment_reference_count_ (rhs.current) : kTerminalSentinel_;
68 return *this;
69 }
70 T& operator* () const
71 {
72 if (current == kTerminalSentinel_) {
73 throw std::logic_error{"invalid iterator"};
74 }
75 return current->value;
76 }
77 T* operator->() const
78 {
79 if (current == kTerminalSentinel_) {
80 throw std::logic_error{"invalid iterator"};
81 }
82 return &current->value;
83 }
84 ForwardIterator_& operator++ ()
85 {
86 Assert (current != kTerminalSentinel_); // this is the end()
87 node_* temp = new_ownership_ (current->next);
88 std::swap (current, temp);
89 if (temp != kTerminalSentinel_) {
90 decrement_reference_count_ (temp); // discard newly created ownership
91 }
92 return *this;
93 }
94 ForwardIterator_ operator++ (int)
95 {
96 Assert (current != kTerminalSentinel_); // this is the end()
97 ForwardIterator_ temp = *this;
98 ++*this;
99 return temp;
100 }
101 friend void swap (ForwardIterator_& a, ForwardIterator_& b) noexcept
102 {
103 using std::swap; // bring in swap for built-in types
104 swap (a.current, b.current);
105 }
106 operator ForwardIterator_<const T> () const
107 {
108 return ForwardIterator_<const T> (current);
109 }
110 bool operator== (const ForwardIterator_& rhs) const
111 {
112 return current == rhs.current;
113 }
114 };
115 // static_assert (weakly_incrementable<forward_list<int>::const_iterator>); // fails cuz cannot take difference of two iterators...
116
117 /*
118 ********************************************************************************
119 ***************** LockFreeDataStructures::forward_list<T>::node_ ***************
120 ********************************************************************************
121 */
122 template <typename T>
123 class forward_list<T>::node_ {
124 public:
125 friend class ForwardIterator_<T>;
126 T value;
127 std::atomic<node_*> next;
128 std::atomic<int> referenceCount; // for keeping a node_ referenced by an iterator alive
129
130 node_ (T const& value)
131 : value{value}
132 , next{kTerminalSentinel_}
133 , referenceCount{1}
134 {
135 }
136 node_ (T&& value)
137 : value{std::move (value)}
138 , next{kTerminalSentinel_}
139 , referenceCount{1}
140 {
141 }
142 template <typename... U>
143 node_ (U&&... params)
144 : value{std::forward<U> (params)...}
145 , next{kTerminalSentinel_}
146 , referenceCount{1}
147 {
148 }
149 ~node_ ()
150 {
151 node_* n = owner_lock_ (next); // change next to kSpinSentinel_
152 if (n != kTerminalSentinel_) {
153 decrement_reference_count_ (n); // release ownership of next
154 next.store (kTerminalSentinel_, std::memory_order_relaxed); // relaxed because observers will see kSpinSentinel_
155 }
156 }
157 };
158
159 /*
160 ********************************************************************************
161 ***************** LockFreeDataStructures::forward_list<T> **********************
162 ********************************************************************************
163 */
164 template <typename T>
165 inline forward_list<T>::forward_list ()
166 : fFirst_{kTerminalSentinel_}
167 {
168 }
169 template <typename T>
170 inline forward_list<T>::forward_list (forward_list const& src)
171 : forward_list{}
172 {
173 std::atomic<node_*>* nextPtr = &fFirst_;
174 for (auto const& v : src) {
175 std::atomic<node_*>& next = *nextPtr;
176 node_* newNode = new node_{v};
177 next.store (newNode);
178 nextPtr = &newNode->next;
179 }
180 }
181 template <typename T>
182 inline forward_list<T>::forward_list (forward_list&& src) noexcept
183 : forward_list{}
184 {
185 exchange_ (src.fFirst_, fFirst_);
186 }
187 template <typename T>
188 inline forward_list<T>::~forward_list ()
189 {
190 clear ();
191 }
192 template <typename T>
193 inline bool forward_list<T>::empty () const noexcept
194 {
195 return fFirst_.load () == kTerminalSentinel_;
196 }
197 template <typename T>
199 {
200 node_* current = kTerminalSentinel_;
201 // detach the elements fFirst_ so that blocking is minimal
202 exchange_ (fFirst_, current);
203 if (current == kTerminalSentinel_) {
204 return 0;
205 }
206 // if we just delete the fFirst_ node_, it may cascade down all the
207 // subsequent nodes. This would be fine, if not for the possibility
208 // of blowing the stack. Instead we delete them in reverse.
209 std::vector<node_*> nodes;
210 while (current != kTerminalSentinel_) {
211 nodes.push_back (current);
212 node_* temp = kTerminalSentinel_;
213 // take ownership of the next node_
214 exchange_ (current->next, temp);
215 current = temp;
216 }
217 for (auto i = nodes.rbegin (); i != nodes.rend (); ++i) {
218 decrement_reference_count_ (*i);
219 }
220 return nodes.size (); // return number of deleted elements
221 }
222 template <typename T>
224 {
225 std::list<node_*> nodes;
226 {
227 node_* i = kTerminalSentinel_;
228 exchange_ (fFirst_, i);
229 while (i) {
230 nodes.push_back (i);
231 node_* temp = kSpinSentinel_;
232 exchange_ (i->next, temp); // lock all the nodes
233 i = temp;
234 }
235 }
236 for (auto const& i = nodes.begin (); i != nodes.end (); ++i) {
237 decrement_reference_count_ (*i); // remove prior nodes reference
238 i->next.store (kTerminalSentinel_, std::memory_order_relaxed); // unlink, relaxed because observers will see kSpinSentinel_
239 }
240 return nodes.size (); // return number of deleted elements
241 }
242 template <typename T>
243 inline auto forward_list<T>::front () -> reference
244 {
245 return *begin ();
246 }
247 template <typename T>
248 inline auto forward_list<T>::front () const -> const_reference
249 {
250 return *begin ();
251 }
252 template <typename T>
253 inline auto forward_list<T>::push_front (const T& value) -> iterator
254 {
255 auto* nodePtr = new node_{value};
256 return insert_node_ (fFirst_, nodePtr);
257 }
258 template <typename T>
259 inline auto forward_list<T>::push_front (T&& value) -> iterator
260 {
261 return insert_node_ (fFirst_, new node_{std::move (value)});
262 }
263 template <typename T>
264 template <typename... U>
265 inline auto forward_list<T>::emplace_front (U&&... params) -> reference
266 {
267 return *insert_node_ (fFirst_, new node_{std::forward<U> (params)...});
268 }
269 template <typename T>
270 inline std::optional<T> forward_list<T>::pop_front ()
271 {
272 T r{};
273 return remove_node_ (fFirst_, &r) ? r : std::optional<T>{};
274 }
275 template <typename T>
276 inline auto forward_list<T>::begin () -> iterator
277 {
278 node_* n = new_ownership_ (fFirst_); // wait for unlock
279 iterator result{n};
280 if (n != kTerminalSentinel_) {
281 decrement_reference_count_ (n); // discard newly created ownership
282 }
283 return result;
284 }
285 template <typename T>
286 auto forward_list<T>::begin () const -> const_iterator
287 {
288 // const_cast is needed to lock fFirst_
289 std::atomic<node_*>& nonConstFirst = *const_cast<std::atomic<node_*>*> (&fFirst_);
290 node_* n = new_ownership_ (nonConstFirst);
291 if (n == kTerminalSentinel_) {
292 return end ();
293 }
294 const_iterator result{n};
295 decrement_reference_count_ (n);
296 return result;
297 }
298 template <typename T>
299 inline auto forward_list<T>::cbegin () const -> const_iterator
300 {
301 // add const using const_cast to invoke the const version of begin
302 return const_cast<forward_list const*> (this)->begin ();
303 }
304 template <typename T>
305 inline auto forward_list<T>::end () -> iterator
306 {
307 return iterator{};
308 }
309 template <typename T>
310 inline auto forward_list<T>::end () const -> const_iterator
311 {
312 return const_iterator{};
313 }
314 template <typename T>
315 inline auto forward_list<T>::cend () const -> const_iterator
316 {
317 return const_iterator{};
318 }
319 template <typename T>
320 inline auto forward_list<T>::insert_after (const_iterator position, T const& value) -> iterator
321 {
322 return insert_node_ (position.current->next, new node_{value});
323 }
324 template <typename T>
325 inline auto forward_list<T>::insert_after (const_iterator position, T&& value) -> iterator
326 {
327 return insert_node_ (position.current->next, new node_{value});
328 }
329 template <typename T>
330 auto forward_list<T>::insert_after (const_iterator pos, int count, const T& value) -> iterator
331 {
332 if (count <= 0)
333 return iterator{};
334 iterator result = pos = insert_after (pos, value);
335 for (int i = 1; i < count; ++i) {
336 pos = insert_after (pos, value);
337 }
338 return result;
339 }
340 template <typename T>
341 template <typename InputIt>
342 auto forward_list<T>::insert_after (const_iterator pos, InputIt first, InputIt last) -> iterator
343 {
344 while (first != last) {
345 pos = insert_after (pos, first);
346 ++first;
347 }
348 return pos;
349 }
350 template <typename T>
351 inline auto forward_list<T>::insert_after (const_iterator pos, std::initializer_list<T> ilist) -> iterator
352 {
353 return insert_after (pos, ilist.begin (), ilist.end ());
354 }
355 template <typename T>
356 template <typename... U>
357 inline auto forward_list<T>::emplace_after (const_iterator position, U&&... params) -> iterator
358 {
359 return insert_node_ (position, new node_{std::forward (params)...});
360 }
361 template <typename T>
362 bool forward_list<T>::separate_after (const_iterator position, forward_list<T>*& result)
363 {
364 node_* n = separate_ (position.current->next);
365 if (!n)
366 return false;
367 result = new forward_list<T>{};
368 result->fFirst_ = n;
369 return true;
370 }
371 template <typename T>
372 inline bool forward_list<T>::erase_after (const_iterator position, T* value)
373 {
374 return remove_node_ (position.current->next, value);
375 }
376 template <typename T>
377 inline void forward_list<T>::swap (forward_list& other) noexcept
378 {
379 exchange_ (fFirst_, other.fFirst_);
380 }
381 template <typename T>
382 bool forward_list<T>::remove_node_ (std::atomic<node_*>& atomic_ptr, T* value)
383 {
384 node_* x = owner_lock_ (atomic_ptr);
385 if (x == kTerminalSentinel_) {
386 if (atomic_ptr.load () == kTerminalSentinel_)
387 return false;
388 node_* temp = kTerminalSentinel_;
389 owner_unlock_ (atomic_ptr, temp);
390 return false;
391 }
392 *value = x->value;
393 node_* y = owner_lock_ (x->next);
394 owner_unlock_ (atomic_ptr, y);
395 x->next.store (kTerminalSentinel_);
396 decrement_reference_count_ (x);
397 return true;
398 }
399 template <typename T>
400 inline auto forward_list<T>::insert_node_ (std::atomic<node_*>& atomic_ptr, node_* n) -> iterator
401 {
402 iterator result{n}; // it's possible that the node_ is removed before we return, so do this early
403 n->next.store (n);
404 exchange_ (n->next, atomic_ptr);
405 return result;
406 }
407 template <typename T>
408 inline auto forward_list<T>::separate_ (std::atomic<node_*>& atomic_ptr) -> node_*
409 {
410 node_* oldNext = kTerminalSentinel_;
411 exchange_ (atomic_ptr, oldNext);
412 return oldNext;
413 }
414 template <typename T>
415 inline void forward_list<T>::decrement_reference_count_ (node_*& n)
416 {
417 Assert (n != nullptr);
418 Assert (n != kTerminalSentinel_); // not a valid node_
419 Assert (n != kSpinSentinel_); // not a valid node_
420 if (n->referenceCount-- == 1) {
421 delete n;
422 }
423 n = nullptr;
424 }
425 template <typename T>
426 inline auto forward_list<T>::increment_reference_count_ (node_* n) -> node_*
427 {
428 Assert (n != nullptr); //must be a valid node_ because ownership is a precondition
429 Assert (n != kTerminalSentinel_);
430 Assert (n != kSpinSentinel_);
431 ++n->referenceCount;
432 return n;
433 }
434 template <typename T>
435 inline auto forward_list<T>::spin_get_ (const std::atomic<node_*>& n) -> node_*
436 {
437 while (true) {
438 auto p = n.load ();
439 if (p != kSpinSentinel_) {
440 return p;
441 }
442 }
443 }
444 template <typename T>
445 void forward_list<T>::exchange_ (std::atomic<node_*>& left, node_*& right)
446 {
447 Assert (right != nullptr);
448 Assert (right != kSpinSentinel_); // invalid node_
449 node_* n = left.load ();
450 do {
451 n = spin_get_ (left);
452 } while (!left.compare_exchange_weak (n, right));
453 Assert (n != nullptr);
454 right = n;
455 }
456 template <typename T>
457 void forward_list<T>::exchange_ (std::atomic<node_*>& left, std::atomic<node_*>& right)
458 {
459 node_* temp = owner_lock_ (left);
460 exchange_ (right, temp);
461 if (temp != kTerminalSentinel_) {
462 owner_unlock_ (left, temp);
463 }
464 else {
465 left.store (kTerminalSentinel_);
466 }
467 }
468 template <typename T>
469 auto forward_list<T>::owner_lock_ (std::atomic<node_*>& atomic_ptr) -> node_*
470 {
471 node_* n = atomic_ptr.load ();
472 do {
473 n = spin_get_ (atomic_ptr);
474 } while (!atomic_ptr.compare_exchange_weak (n, kSpinSentinel_));
475
476 if (n == kTerminalSentinel_) { // the node_ has been deleted already
477 // put terminal_ back in to owner_unlock_
478 atomic_ptr.store (kTerminalSentinel_, std::memory_order_relaxed); // relaxed because observers will see kSpinSentinel_
479 return kTerminalSentinel_;
480 } // else stays locked
481 return n;
482 }
483 template <typename T>
484 inline void forward_list<T>::owner_unlock_ (std::atomic<node_*>& atomic_ptr, node_*& n)
485 {
486 Assert (n != nullptr);
487 Assert (n != kSpinSentinel_);
488 Assert (atomic_ptr.load (std::memory_order_relaxed) == kSpinSentinel_); // relaxed because it was set to kSpinSentinel_ by the current thread
489 atomic_ptr.store (n, std::memory_order_relaxed); // relaxed because observers will see kSpinSentinel_
490 n = nullptr; // make sure the caller cant use the pointer anymore
491 }
492 template <typename T>
493 auto forward_list<T>::new_ownership_ (std::atomic<node_*>& atomic_ptr) -> node_*
494 {
495 node_* temp = owner_lock_ (atomic_ptr);
496 if (temp == kTerminalSentinel_) {
497 return kTerminalSentinel_;
498 }
499 node_* result = temp != kTerminalSentinel_ ? increment_reference_count_ (temp) : kTerminalSentinel_;
500 owner_unlock_ (atomic_ptr, temp);
501 return result;
502 }
503
504}
505
506namespace std {
507 template <typename T>
510 {
511 lhs.swap (rhs);
512 }
513}
Include this file VERY EARLY ON - before including stuff like <cstdio> - to allow use of Valgrind (so...
lock-free, threadsafe singly linked list implementation (patterned after std::forward_list) - INTERNA...
nonvirtual forward_list & operator=(const forward_list &rhs)=delete
STL namespace.