| // Copyright (c) 2019 The Chromium Authors. All rights reserved. | |
| // Use of this source code is governed by a BSD-style license that can be | |
| // found in the LICENSE file. | |
| #ifndef QUICHE_SPDY_CORE_SPDY_INTRUSIVE_LIST_H_ | |
| #define QUICHE_SPDY_CORE_SPDY_INTRUSIVE_LIST_H_ | |
| // A SpdyIntrusiveList<> is a doubly-linked list where the link pointers are | |
| // embedded in the elements. They are circularly linked making insertion and | |
| // removal into a known position constant time and branch-free operations. | |
| // | |
| // Usage is similar to an STL list<> where feasible, but there are important | |
| // differences. First and foremost, the elements must derive from the | |
| // SpdyIntrusiveLink<> base class: | |
| // | |
| // struct Foo : public SpdyIntrusiveLink<Foo> { | |
| // // ... | |
| // } | |
| // | |
| // SpdyIntrusiveList<Foo> l; | |
| // l.push_back(new Foo); | |
| // l.push_front(new Foo); | |
| // l.erase(&l.front()); | |
| // l.erase(&l.back()); | |
| // | |
| // Intrusive lists are primarily useful when you would have considered embedding | |
| // link pointers in your class directly for space or performance reasons. An | |
| // SpdyIntrusiveLink<> is the size of 2 pointers, usually 16 bytes on 64-bit | |
| // systems. Intrusive lists do not perform memory allocation (unlike the STL | |
| // list<> class) and thus may use less memory than list<>. In particular, if the | |
| // list elements are pointers to objects, using a list<> would perform an extra | |
| // memory allocation for each list node structure, while an SpdyIntrusiveList<> | |
| // would not. | |
| // | |
| // Note that SpdyIntrusiveLink is exempt from the C++ style guide's limitations | |
| // on multiple inheritance, so it's fine to inherit from both SpdyIntrusiveLink | |
| // and a base class, even if the base class is not a pure interface. | |
| // | |
| // Because the list pointers are embedded in the objects stored in an | |
| // SpdyIntrusiveList<>, erasing an item from a list is constant time. Consider | |
| // the following: | |
| // | |
| // map<string,Foo> foo_map; | |
| // list<Foo*> foo_list; | |
| // | |
| // foo_list.push_back(&foo_map["bar"]); | |
| // foo_list.erase(&foo_map["bar"]); // Compile error! | |
| // | |
| // The problem here is that a Foo* doesn't know where on foo_list it resides, | |
| // so removal requires iteration over the list. Various tricks can be performed | |
| // to overcome this. For example, a foo_list::iterator can be stored inside of | |
| // the Foo object. But at that point you'd be better off using an | |
| // SpdyIntrusiveList<>: | |
| // | |
| // map<string,Foo> foo_map; | |
| // SpdyIntrusiveList<Foo> foo_list; | |
| // | |
| // foo_list.push_back(&foo_map["bar"]); | |
| // foo_list.erase(&foo_map["bar"]); // Yeah! | |
| // | |
| // Note that SpdyIntrusiveLists come with a few limitations. The primary | |
| // limitation is that the SpdyIntrusiveLink<> base class is not copyable or | |
| // assignable. The result is that STL algorithms which mutate the order of | |
| // iterators, such as reverse() and unique(), will not work by default with | |
| // SpdyIntrusiveLists. In order to allow these algorithms to work you'll need to | |
| // define swap() and/or operator= for your class. | |
| // | |
| // Another limitation is that the SpdyIntrusiveList<> structure itself is not | |
| // copyable or assignable since an item/link combination can only exist on one | |
| // SpdyIntrusiveList<> at a time. This limitation is a result of the link | |
| // pointers for an item being intrusive in the item itself. For example, the | |
| // following will not compile: | |
| // | |
| // FooList a; | |
| // FooList b(a); // no copy constructor | |
| // b = a; // no assignment operator | |
| // | |
| // The similar STL code does work since the link pointers are external to the | |
| // item: | |
| // | |
| // list<int*> a; | |
| // a.push_back(new int); | |
| // list<int*> b(a); | |
| // CHECK(a.front() == b.front()); | |
| // | |
| // Note that SpdyIntrusiveList::size() runs in O(N) time. | |
| #include <stddef.h> | |
| #include <iterator> | |
| namespace spdy { | |
| template <typename T, typename ListID> class SpdyIntrusiveList; | |
| template <typename T, typename ListID = void> class SpdyIntrusiveLink { | |
| protected: | |
| // We declare the constructor protected so that only derived types and the | |
| // befriended list can construct this. | |
| SpdyIntrusiveLink() : next_(nullptr), prev_(nullptr) {} | |
| #ifndef SWIG | |
| SpdyIntrusiveLink(const SpdyIntrusiveLink&) = delete; | |
| SpdyIntrusiveLink& operator=(const SpdyIntrusiveLink&) = delete; | |
| #endif // SWIG | |
| private: | |
| // We befriend the matching list type so that it can manipulate the links | |
| // while they are kept private from others. | |
| friend class SpdyIntrusiveList<T, ListID>; | |
| // Encapsulates the logic to convert from a link to its derived type. | |
| T* cast_to_derived() { return static_cast<T*>(this); } | |
| const T* cast_to_derived() const { return static_cast<const T*>(this); } | |
| SpdyIntrusiveLink* next_; | |
| SpdyIntrusiveLink* prev_; | |
| }; | |
| template <typename T, typename ListID = void> class SpdyIntrusiveList { | |
| template <typename QualifiedT, typename QualifiedLinkT> class iterator_impl; | |
| public: | |
| typedef T value_type; | |
| typedef value_type *pointer; | |
| typedef const value_type *const_pointer; | |
| typedef value_type &reference; | |
| typedef const value_type &const_reference; | |
| typedef size_t size_type; | |
| typedef ptrdiff_t difference_type; | |
| typedef SpdyIntrusiveLink<T, ListID> link_type; | |
| typedef iterator_impl<T, link_type> iterator; | |
| typedef iterator_impl<const T, const link_type> const_iterator; | |
| typedef std::reverse_iterator<const_iterator> const_reverse_iterator; | |
| typedef std::reverse_iterator<iterator> reverse_iterator; | |
| SpdyIntrusiveList() { clear(); } | |
| // After the move constructor the moved-from list will be empty. | |
| // | |
| // NOTE: There is no move assign operator (for now). | |
| // The reason is that at the moment 'clear()' does not unlink the nodes. | |
| // It makes is_linked() return true when it should return false. | |
| // If such node is removed from the list (e.g. from its destructor), or is | |
| // added to another list - a memory corruption will occur. | |
| // Admitedly the destructor does not unlink the nodes either, but move-assign | |
| // will likely make the problem more prominent. | |
| #ifndef SWIG | |
| SpdyIntrusiveList(SpdyIntrusiveList&& src) noexcept { | |
| clear(); | |
| if (src.empty()) return; | |
| sentinel_link_.next_ = src.sentinel_link_.next_; | |
| sentinel_link_.prev_ = src.sentinel_link_.prev_; | |
| // Fix head and tail nodes of the list. | |
| sentinel_link_.prev_->next_ = &sentinel_link_; | |
| sentinel_link_.next_->prev_ = &sentinel_link_; | |
| src.clear(); | |
| } | |
| #endif // SWIG | |
| iterator begin() { return iterator(sentinel_link_.next_); } | |
| const_iterator begin() const { return const_iterator(sentinel_link_.next_); } | |
| iterator end() { return iterator(&sentinel_link_); } | |
| const_iterator end() const { return const_iterator(&sentinel_link_); } | |
| reverse_iterator rbegin() { return reverse_iterator(end()); } | |
| const_reverse_iterator rbegin() const { | |
| return const_reverse_iterator(end()); | |
| } | |
| reverse_iterator rend() { return reverse_iterator(begin()); } | |
| const_reverse_iterator rend() const { | |
| return const_reverse_iterator(begin()); | |
| } | |
| bool empty() const { return (sentinel_link_.next_ == &sentinel_link_); } | |
| // This runs in O(N) time. | |
| size_type size() const { return std::distance(begin(), end()); } | |
| size_type max_size() const { return size_type(-1); } | |
| reference front() { return *begin(); } | |
| const_reference front() const { return *begin(); } | |
| reference back() { return *(--end()); } | |
| const_reference back() const { return *(--end()); } | |
| static iterator insert(iterator position, T *obj) { | |
| return insert_link(position.link(), obj); | |
| } | |
| void push_front(T* obj) { insert(begin(), obj); } | |
| void push_back(T* obj) { insert(end(), obj); } | |
| static iterator erase(T* obj) { | |
| link_type* obj_link = obj; | |
| // Fix up the next and previous links for the previous and next objects. | |
| obj_link->next_->prev_ = obj_link->prev_; | |
| obj_link->prev_->next_ = obj_link->next_; | |
| // Zero out the next and previous links for the removed item. This will | |
| // cause any future attempt to remove the item from the list to cause a | |
| // crash instead of possibly corrupting the list structure. | |
| link_type* next_link = obj_link->next_; | |
| obj_link->next_ = nullptr; | |
| obj_link->prev_ = nullptr; | |
| return iterator(next_link); | |
| } | |
| static iterator erase(iterator position) { | |
| return erase(position.operator->()); | |
| } | |
| void pop_front() { erase(begin()); } | |
| void pop_back() { erase(--end()); } | |
| // Check whether the given element is linked into some list. Note that this | |
| // does *not* check whether it is linked into a particular list. | |
| // Also, if clear() is used to clear the containing list, is_linked() will | |
| // still return true even though obj is no longer in any list. | |
| static bool is_linked(const T* obj) { | |
| return obj->link_type::next_ != nullptr; | |
| } | |
| void clear() { | |
| sentinel_link_.next_ = sentinel_link_.prev_ = &sentinel_link_; | |
| } | |
| void swap(SpdyIntrusiveList& x) { | |
| SpdyIntrusiveList tmp; | |
| tmp.splice(tmp.begin(), *this); | |
| this->splice(this->begin(), x); | |
| x.splice(x.begin(), tmp); | |
| } | |
| void splice(iterator pos, SpdyIntrusiveList& src) { | |
| splice(pos, src.begin(), src.end()); | |
| } | |
| void splice(iterator pos, iterator i) { splice(pos, i, std::next(i)); } | |
| void splice(iterator pos, iterator first, iterator last) { | |
| if (first == last) return; | |
| link_type* const last_prev = last.link()->prev_; | |
| // Remove from the source. | |
| first.link()->prev_->next_ = last.operator->(); | |
| last.link()->prev_ = first.link()->prev_; | |
| // Attach to the destination. | |
| first.link()->prev_ = pos.link()->prev_; | |
| pos.link()->prev_->next_ = first.operator->(); | |
| last_prev->next_ = pos.operator->(); | |
| pos.link()->prev_ = last_prev; | |
| } | |
| private: | |
| static iterator insert_link(link_type* next_link, T* obj) { | |
| link_type* obj_link = obj; | |
| obj_link->next_ = next_link; | |
| link_type* const initial_next_prev = next_link->prev_; | |
| obj_link->prev_ = initial_next_prev; | |
| initial_next_prev->next_ = obj_link; | |
| next_link->prev_ = obj_link; | |
| return iterator(obj_link); | |
| } | |
| // The iterator implementation is parameterized on a potentially qualified | |
| // variant of T and the matching qualified link type. Essentially, QualifiedT | |
| // will either be 'T' or 'const T', the latter for a const_iterator. | |
| template <typename QualifiedT, typename QualifiedLinkT> | |
| class iterator_impl : public std::iterator<std::bidirectional_iterator_tag, | |
| QualifiedT> { | |
| public: | |
| typedef std::iterator<std::bidirectional_iterator_tag, QualifiedT> base; | |
| iterator_impl() : link_(nullptr) {} | |
| iterator_impl(QualifiedLinkT* link) : link_(link) {} | |
| iterator_impl(const iterator_impl& x) : link_(x.link_) {} | |
| // Allow converting and comparing across iterators where the pointer | |
| // assignment and comparisons (respectively) are allowed. | |
| template <typename U, typename V> | |
| iterator_impl(const iterator_impl<U, V>& x) : link_(x.link_) {} | |
| template <typename U, typename V> | |
| bool operator==(const iterator_impl<U, V>& x) const { | |
| return link_ == x.link_; | |
| } | |
| template <typename U, typename V> | |
| bool operator!=(const iterator_impl<U, V>& x) const { | |
| return link_ != x.link_; | |
| } | |
| typename base::reference operator*() const { return *operator->(); } | |
| typename base::pointer operator->() const { | |
| return link_->cast_to_derived(); | |
| } | |
| QualifiedLinkT *link() const { return link_; } | |
| #ifndef SWIG // SWIG can't wrap these operator overloads. | |
| iterator_impl& operator++() { link_ = link_->next_; return *this; } | |
| iterator_impl operator++(int /*unused*/) { | |
| iterator_impl tmp = *this; | |
| ++*this; | |
| return tmp; | |
| } | |
| iterator_impl& operator--() { link_ = link_->prev_; return *this; } | |
| iterator_impl operator--(int /*unused*/) { | |
| iterator_impl tmp = *this; | |
| --*this; | |
| return tmp; | |
| } | |
| #endif // SWIG | |
| private: | |
| // Ensure iterators can access other iterators node directly. | |
| template <typename U, typename V> friend class iterator_impl; | |
| QualifiedLinkT* link_; | |
| }; | |
| // This bare link acts as the sentinel node. | |
| link_type sentinel_link_; | |
| // These are private and undefined to prevent copying and assigning. | |
| SpdyIntrusiveList(const SpdyIntrusiveList&); | |
| void operator=(const SpdyIntrusiveList&); | |
| }; | |
| } // namespace spdy | |
| #endif // QUICHE_SPDY_CORE_SPDY_INTRUSIVE_LIST_H_ |