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