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