jpayne@69
|
1 // Copyright (c) 2021 Cloudflare, Inc. and contributors
|
jpayne@69
|
2 // Licensed under the MIT License:
|
jpayne@69
|
3 //
|
jpayne@69
|
4 // Permission is hereby granted, free of charge, to any person obtaining a copy
|
jpayne@69
|
5 // of this software and associated documentation files (the "Software"), to deal
|
jpayne@69
|
6 // in the Software without restriction, including without limitation the rights
|
jpayne@69
|
7 // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
jpayne@69
|
8 // copies of the Software, and to permit persons to whom the Software is
|
jpayne@69
|
9 // furnished to do so, subject to the following conditions:
|
jpayne@69
|
10 //
|
jpayne@69
|
11 // The above copyright notice and this permission notice shall be included in
|
jpayne@69
|
12 // all copies or substantial portions of the Software.
|
jpayne@69
|
13 //
|
jpayne@69
|
14 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
jpayne@69
|
15 // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
jpayne@69
|
16 // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
jpayne@69
|
17 // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
jpayne@69
|
18 // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
jpayne@69
|
19 // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
jpayne@69
|
20 // THE SOFTWARE.
|
jpayne@69
|
21
|
jpayne@69
|
22 #pragma once
|
jpayne@69
|
23
|
jpayne@69
|
24 #include "common.h"
|
jpayne@69
|
25
|
jpayne@69
|
26 KJ_BEGIN_HEADER
|
jpayne@69
|
27
|
jpayne@69
|
28 namespace kj {
|
jpayne@69
|
29
|
jpayne@69
|
30 template <typename T>
|
jpayne@69
|
31 class ListLink;
|
jpayne@69
|
32
|
jpayne@69
|
33 template <typename T, typename MaybeConstT, ListLink<T> T::*link>
|
jpayne@69
|
34 class ListIterator;
|
jpayne@69
|
35
|
jpayne@69
|
36 namespace _ { // (private)
|
jpayne@69
|
37
|
jpayne@69
|
38 KJ_NORETURN(void throwDoubleAdd());
|
jpayne@69
|
39 KJ_NORETURN(void throwRemovedNotPresent());
|
jpayne@69
|
40 KJ_NORETURN(void throwRemovedWrongList());
|
jpayne@69
|
41 KJ_NORETURN(void throwDestroyedWhileInList());
|
jpayne@69
|
42
|
jpayne@69
|
43 } // namespace _ (private)
|
jpayne@69
|
44
|
jpayne@69
|
45 template <typename T, ListLink<T> T::*link>
|
jpayne@69
|
46 class List {
|
jpayne@69
|
47 // A linked list that does no memory allocation.
|
jpayne@69
|
48 //
|
jpayne@69
|
49 // The list contains elements of type T that are allocated elsewhere. An existing object of type
|
jpayne@69
|
50 // T can be added to the list and removed again without doing any heap allocation. This is
|
jpayne@69
|
51 // achieved by requiring that T contains a field of type ListLink<T>. A pointer-to-member to
|
jpayne@69
|
52 // this field is the second parameter to the `List` template.
|
jpayne@69
|
53 //
|
jpayne@69
|
54 // kj::List is ideally suited to situations where an object wants to be able to "add itself" to
|
jpayne@69
|
55 // a list of objects waiting for a notification, with the ability to remove itself early if it
|
jpayne@69
|
56 // wants to stop waiting. With traditional STL containers, these operations would require memory
|
jpayne@69
|
57 // allocation.
|
jpayne@69
|
58 //
|
jpayne@69
|
59 // Example:
|
jpayne@69
|
60 //
|
jpayne@69
|
61 // struct Item {
|
jpayne@69
|
62 // ListLink<Waiter> link;
|
jpayne@69
|
63 // // ... other members ...
|
jpayne@69
|
64 // };
|
jpayne@69
|
65 //
|
jpayne@69
|
66 // kj::List<Item, &Item::link> itemList;
|
jpayne@69
|
67 //
|
jpayne@69
|
68 // Item foo;
|
jpayne@69
|
69 // itemList.add(foo);
|
jpayne@69
|
70 // itemList.remove(foo);
|
jpayne@69
|
71 //
|
jpayne@69
|
72 // Note that you MUST manually remove an element from the list before destroying it. ListLinks
|
jpayne@69
|
73 // do not automatically unlink themselves because this could lead to subtle thread-safety bugs
|
jpayne@69
|
74 // if the List is guarded by a mutex, and that mutex is not currently locked. Normally, you should
|
jpayne@69
|
75 // have T's destructor remove it from any lists. You can use `link.isLinked()` to check if the
|
jpayne@69
|
76 // item is currently in a list.
|
jpayne@69
|
77 //
|
jpayne@69
|
78 // kj::List is a doubly-linked list in order to allow O(1) removal of any element given only a
|
jpayne@69
|
79 // reference to the element. However, it only supports forward iteration.
|
jpayne@69
|
80 //
|
jpayne@69
|
81 // When iterating over a kj::List, you can safely remove current element which the iterator
|
jpayne@69
|
82 // points to without breaking the iteration. However, removing any *other* element could
|
jpayne@69
|
83 // invalidate the iterator.
|
jpayne@69
|
84
|
jpayne@69
|
85 public:
|
jpayne@69
|
86 List() = default;
|
jpayne@69
|
87 KJ_DISALLOW_COPY_AND_MOVE(List);
|
jpayne@69
|
88
|
jpayne@69
|
89 bool empty() const {
|
jpayne@69
|
90 return head == nullptr;
|
jpayne@69
|
91 }
|
jpayne@69
|
92
|
jpayne@69
|
93 size_t size() const {
|
jpayne@69
|
94 return listSize;
|
jpayne@69
|
95 }
|
jpayne@69
|
96
|
jpayne@69
|
97 void add(T& element) {
|
jpayne@69
|
98 if ((element.*link).prev != nullptr) _::throwDoubleAdd();
|
jpayne@69
|
99 *tail = element;
|
jpayne@69
|
100 (element.*link).prev = tail;
|
jpayne@69
|
101 tail = &((element.*link).next);
|
jpayne@69
|
102 ++listSize;
|
jpayne@69
|
103 }
|
jpayne@69
|
104
|
jpayne@69
|
105 void addFront(T& element) {
|
jpayne@69
|
106 if ((element.*link).prev != nullptr) _::throwDoubleAdd();
|
jpayne@69
|
107 (element.*link).next = head;
|
jpayne@69
|
108 (element.*link).prev = &head;
|
jpayne@69
|
109 KJ_IF_MAYBE(oldHead, head) {
|
jpayne@69
|
110 (oldHead->*link).prev = &(element.*link).next;
|
jpayne@69
|
111 } else {
|
jpayne@69
|
112 tail = &(element.*link).next;
|
jpayne@69
|
113 }
|
jpayne@69
|
114 head = element;
|
jpayne@69
|
115 ++listSize;
|
jpayne@69
|
116 }
|
jpayne@69
|
117
|
jpayne@69
|
118 void remove(T& element) {
|
jpayne@69
|
119 if ((element.*link).prev == nullptr) _::throwRemovedNotPresent();
|
jpayne@69
|
120 *((element.*link).prev) = (element.*link).next;
|
jpayne@69
|
121 KJ_IF_MAYBE(n, (element.*link).next) {
|
jpayne@69
|
122 (n->*link).prev = (element.*link).prev;
|
jpayne@69
|
123 } else {
|
jpayne@69
|
124 if (tail != &((element.*link).next)) _::throwRemovedWrongList();
|
jpayne@69
|
125 tail = (element.*link).prev;
|
jpayne@69
|
126 }
|
jpayne@69
|
127 (element.*link).next = nullptr;
|
jpayne@69
|
128 (element.*link).prev = nullptr;
|
jpayne@69
|
129 --listSize;
|
jpayne@69
|
130 }
|
jpayne@69
|
131
|
jpayne@69
|
132 typedef ListIterator<T, T, link> Iterator;
|
jpayne@69
|
133 typedef ListIterator<T, const T, link> ConstIterator;
|
jpayne@69
|
134
|
jpayne@69
|
135 Iterator begin() { return Iterator(head); }
|
jpayne@69
|
136 Iterator end() { return Iterator(nullptr); }
|
jpayne@69
|
137 ConstIterator begin() const { return ConstIterator(head); }
|
jpayne@69
|
138 ConstIterator end() const { return ConstIterator(nullptr); }
|
jpayne@69
|
139
|
jpayne@69
|
140 T& front() { return *begin(); }
|
jpayne@69
|
141 const T& front() const { return *begin(); }
|
jpayne@69
|
142
|
jpayne@69
|
143 private:
|
jpayne@69
|
144 Maybe<T&> head;
|
jpayne@69
|
145 Maybe<T&>* tail = &head;
|
jpayne@69
|
146 size_t listSize = 0;
|
jpayne@69
|
147 };
|
jpayne@69
|
148
|
jpayne@69
|
149 template <typename T>
|
jpayne@69
|
150 class ListLink {
|
jpayne@69
|
151 public:
|
jpayne@69
|
152 ListLink(): next(nullptr), prev(nullptr) {}
|
jpayne@69
|
153 ~ListLink() noexcept {
|
jpayne@69
|
154 // Intentionally `noexcept` because we want to crash if a dangling pointer was left in a list.
|
jpayne@69
|
155 if (prev != nullptr) _::throwDestroyedWhileInList();
|
jpayne@69
|
156 }
|
jpayne@69
|
157 KJ_DISALLOW_COPY_AND_MOVE(ListLink);
|
jpayne@69
|
158
|
jpayne@69
|
159 bool isLinked() const { return prev != nullptr; }
|
jpayne@69
|
160
|
jpayne@69
|
161 private:
|
jpayne@69
|
162 Maybe<T&> next;
|
jpayne@69
|
163 Maybe<T&>* prev;
|
jpayne@69
|
164
|
jpayne@69
|
165 template <typename U, ListLink<U> U::*link>
|
jpayne@69
|
166 friend class List;
|
jpayne@69
|
167 template <typename U, typename MaybeConstU, ListLink<U> U::*link>
|
jpayne@69
|
168 friend class ListIterator;
|
jpayne@69
|
169 };
|
jpayne@69
|
170
|
jpayne@69
|
171 template <typename T, typename MaybeConstT, ListLink<T> T::*link>
|
jpayne@69
|
172 class ListIterator {
|
jpayne@69
|
173 public:
|
jpayne@69
|
174 ListIterator() = default;
|
jpayne@69
|
175
|
jpayne@69
|
176 MaybeConstT& operator*() {
|
jpayne@69
|
177 KJ_IREQUIRE(current != nullptr, "tried to dereference end of list");
|
jpayne@69
|
178 return *_::readMaybe(current);
|
jpayne@69
|
179 }
|
jpayne@69
|
180 const T& operator*() const {
|
jpayne@69
|
181 KJ_IREQUIRE(current != nullptr, "tried to dereference end of list");
|
jpayne@69
|
182 return *_::readMaybe(current);
|
jpayne@69
|
183 }
|
jpayne@69
|
184 MaybeConstT* operator->() {
|
jpayne@69
|
185 KJ_IREQUIRE(current != nullptr, "tried to dereference end of list");
|
jpayne@69
|
186 return _::readMaybe(current);
|
jpayne@69
|
187 }
|
jpayne@69
|
188 const T* operator->() const {
|
jpayne@69
|
189 KJ_IREQUIRE(current != nullptr, "tried to dereference end of list");
|
jpayne@69
|
190 return _::readMaybe(current);
|
jpayne@69
|
191 }
|
jpayne@69
|
192
|
jpayne@69
|
193 inline ListIterator& operator++() {
|
jpayne@69
|
194 current = next;
|
jpayne@69
|
195 next = current.map([](MaybeConstT& obj) -> kj::Maybe<MaybeConstT&> { return (obj.*link).next; })
|
jpayne@69
|
196 .orDefault(nullptr);
|
jpayne@69
|
197 return *this;
|
jpayne@69
|
198 }
|
jpayne@69
|
199 inline ListIterator operator++(int) {
|
jpayne@69
|
200 ListIterator result = *this;
|
jpayne@69
|
201 ++*this;
|
jpayne@69
|
202 return result;
|
jpayne@69
|
203 }
|
jpayne@69
|
204
|
jpayne@69
|
205 inline bool operator==(const ListIterator& other) const {
|
jpayne@69
|
206 return _::readMaybe(current) == _::readMaybe(other.current);
|
jpayne@69
|
207 }
|
jpayne@69
|
208 inline bool operator!=(const ListIterator& other) const {
|
jpayne@69
|
209 return _::readMaybe(current) != _::readMaybe(other.current);
|
jpayne@69
|
210 }
|
jpayne@69
|
211
|
jpayne@69
|
212 private:
|
jpayne@69
|
213 Maybe<MaybeConstT&> current;
|
jpayne@69
|
214
|
jpayne@69
|
215 Maybe<MaybeConstT&> next;
|
jpayne@69
|
216 // so that the current item can be removed from the list without invalidating the iterator
|
jpayne@69
|
217
|
jpayne@69
|
218 explicit ListIterator(Maybe<MaybeConstT&> start)
|
jpayne@69
|
219 : current(start),
|
jpayne@69
|
220 next(start.map([](MaybeConstT& obj) -> kj::Maybe<MaybeConstT&> { return (obj.*link).next; })
|
jpayne@69
|
221 .orDefault(nullptr)) {}
|
jpayne@69
|
222 friend class List<T, link>;
|
jpayne@69
|
223 };
|
jpayne@69
|
224
|
jpayne@69
|
225 } // namespace kj
|
jpayne@69
|
226
|
jpayne@69
|
227 KJ_END_HEADER
|