capnproto

FORK: Cap'n Proto serialization/RPC system - core tools and C++ library
git clone https://git.neptards.moe/neptards/capnproto.git
Log | Files | Refs | README | LICENSE

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