/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ /* vim: set ts=8 sts=2 et sw=2 tw=80: */ /* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ /* A type-safe doubly-linked list class. */ /* * The classes LinkedList and LinkedListElement together form a * convenient, type-safe doubly-linked list implementation. * * The class T which will be inserted into the linked list must inherit from * LinkedListElement. A given object may be in only one linked list at a * time. * * A LinkedListElement automatically removes itself from the list upon * destruction, and a LinkedList will fatally assert in debug builds if it's * non-empty when it's destructed. * * For example, you might use LinkedList in a simple observer list class as * follows. * * class Observer : public LinkedListElement * { * public: * void observe(char* aTopic) { ... } * }; * * class ObserverContainer * { * private: * LinkedList list; * * public: * void addObserver(Observer* aObserver) * { * // Will assert if |aObserver| is part of another list. * list.insertBack(aObserver); * } * * void removeObserver(Observer* aObserver) * { * // Will assert if |aObserver| is not part of some list. * aObserver.remove(); * // Or, will assert if |aObserver| is not part of |list| specifically. * // aObserver.removeFrom(list); * } * * void notifyObservers(char* aTopic) * { * for (Observer* o = list.getFirst(); o != nullptr; o = o->getNext()) { * o->observe(aTopic); * } * } * }; * * Additionally, the class AutoCleanLinkedList is a LinkedList that will * remove and delete each element still within itself upon destruction. Note * that because each element is deleted, elements must have been allocated * using |new|. */ #ifndef mozilla_LinkedList_h #define mozilla_LinkedList_h #include #include #include "mozilla/Assertions.h" #include "mozilla/Attributes.h" #include "mozilla/MemoryReporting.h" #include "mozilla/RefPtr.h" #ifdef __cplusplus namespace mozilla { template class LinkedListElement; namespace detail { /** * LinkedList supports refcounted elements using this adapter class. Clients * using LinkedList> will get a data structure that holds a strong * reference to T as long as T is in the list. */ template struct LinkedListElementTraits { typedef T* RawType; typedef const T* ConstRawType; typedef T* ClientType; typedef const T* ConstClientType; // These static methods are called when an element is added to or removed from // a linked list. It can be used to keep track ownership in lists that are // supposed to own their elements. If elements are transferred from one list // to another, no enter or exit calls happen since the elements still belong // to a list. static void enterList(LinkedListElement* elt) {} static void exitList(LinkedListElement* elt) {} // This method is called when AutoCleanLinkedList cleans itself // during destruction. It can be used to call delete on elements if // the list is the sole owner. static void cleanElement(LinkedListElement* elt) { delete elt->asT(); } }; template struct LinkedListElementTraits> { typedef T* RawType; typedef const T* ConstRawType; typedef RefPtr ClientType; typedef RefPtr ConstClientType; static void enterList(LinkedListElement>* elt) { elt->asT()->AddRef(); } static void exitList(LinkedListElement>* elt) { elt->asT()->Release(); } static void cleanElement(LinkedListElement>* elt) {} }; } /* namespace detail */ template class LinkedList; template class LinkedListElement { typedef typename detail::LinkedListElementTraits Traits; typedef typename Traits::RawType RawType; typedef typename Traits::ConstRawType ConstRawType; typedef typename Traits::ClientType ClientType; typedef typename Traits::ConstClientType ConstClientType; /* * It's convenient that we return nullptr when getNext() or getPrevious() * hits the end of the list, but doing so costs an extra word of storage in * each linked list node (to keep track of whether |this| is the sentinel * node) and a branch on this value in getNext/getPrevious. * * We could get rid of the extra word of storage by shoving the "is * sentinel" bit into one of the pointers, although this would, of course, * have performance implications of its own. * * But the goal here isn't to win an award for the fastest or slimmest * linked list; rather, we want a *convenient* linked list. So we won't * waste time guessing which micro-optimization strategy is best. * * * Speaking of unnecessary work, it's worth addressing here why we wrote * mozilla::LinkedList in the first place, instead of using stl::list. * * The key difference between mozilla::LinkedList and stl::list is that * mozilla::LinkedList stores the mPrev/mNext pointers in the object itself, * while stl::list stores the mPrev/mNext pointers in a list element which * itself points to the object being stored. * * mozilla::LinkedList's approach makes it harder to store an object in more * than one list. But the upside is that you can call next() / prev() / * remove() directly on the object. With stl::list, you'd need to store a * pointer to its iterator in the object in order to accomplish this. Not * only would this waste space, but you'd have to remember to update that * pointer every time you added or removed the object from a list. * * In-place, constant-time removal is a killer feature of doubly-linked * lists, and supporting this painlessly was a key design criterion. */ private: LinkedListElement* mNext; LinkedListElement* mPrev; const bool mIsSentinel; public: LinkedListElement() : mNext(this), mPrev(this), mIsSentinel(false) {} /* * Moves |aOther| into |*this|. If |aOther| is already in a list, then * |aOther| is removed from the list and replaced by |*this|. */ LinkedListElement(LinkedListElement&& aOther) : mIsSentinel(aOther.mIsSentinel) { adjustLinkForMove(std::move(aOther)); } LinkedListElement& operator=(LinkedListElement&& aOther) { MOZ_ASSERT(mIsSentinel == aOther.mIsSentinel, "Mismatch NodeKind!"); MOZ_ASSERT(!isInList(), "Assigning to an element in a list messes up that list!"); adjustLinkForMove(std::move(aOther)); return *this; } ~LinkedListElement() { if (!mIsSentinel && isInList()) { remove(); } } /* * Get the next element in the list, or nullptr if this is the last element * in the list. */ RawType getNext() { return mNext->asT(); } ConstRawType getNext() const { return mNext->asT(); } /* * Get the previous element in the list, or nullptr if this is the first * element in the list. */ RawType getPrevious() { return mPrev->asT(); } ConstRawType getPrevious() const { return mPrev->asT(); } /* * Insert aElem after this element in the list. |this| must be part of a * linked list when you call setNext(); otherwise, this method will assert. */ void setNext(RawType aElem) { MOZ_ASSERT(isInList()); setNextUnsafe(aElem); } /* * Insert aElem before this element in the list. |this| must be part of a * linked list when you call setPrevious(); otherwise, this method will * assert. */ void setPrevious(RawType aElem) { MOZ_ASSERT(isInList()); setPreviousUnsafe(aElem); } /* * Remove this element from the list which contains it. If this element is * not currently part of a linked list, this method asserts. */ void remove() { MOZ_ASSERT(isInList()); mPrev->mNext = mNext; mNext->mPrev = mPrev; mNext = this; mPrev = this; Traits::exitList(this); } /* * Remove this element from the list containing it. Returns a pointer to the * element that follows this element (before it was removed). This method * asserts if the element does not belong to a list. Note: In a refcounted * list, |this| may be destroyed. */ RawType removeAndGetNext() { RawType r = getNext(); remove(); return r; } /* * Remove this element from the list containing it. Returns a pointer to the * previous element in the containing list (before the removal). This method * asserts if the element does not belong to a list. Note: In a refcounted * list, |this| may be destroyed. */ RawType removeAndGetPrevious() { RawType r = getPrevious(); remove(); return r; } /* * Identical to remove(), but also asserts in debug builds that this element * is in aList. */ void removeFrom(const LinkedList& aList) { aList.assertContains(asT()); remove(); } /* * Return true if |this| part is of a linked list, and false otherwise. */ bool isInList() const { MOZ_ASSERT((mNext == this) == (mPrev == this)); return mNext != this; } private: friend class LinkedList; friend struct detail::LinkedListElementTraits; enum class NodeKind { Normal, Sentinel }; explicit LinkedListElement(NodeKind nodeKind) : mNext(this), mPrev(this), mIsSentinel(nodeKind == NodeKind::Sentinel) {} /* * Return |this| cast to T* if we're a normal node, or return nullptr if * we're a sentinel node. */ RawType asT() { return mIsSentinel ? nullptr : static_cast(this); } ConstRawType asT() const { return mIsSentinel ? nullptr : static_cast(this); } /* * Insert aElem after this element, but don't check that this element is in * the list. This is called by LinkedList::insertFront(). */ void setNextUnsafe(RawType aElem) { LinkedListElement* listElem = static_cast(aElem); MOZ_ASSERT(!listElem->isInList()); listElem->mNext = this->mNext; listElem->mPrev = this; this->mNext->mPrev = listElem; this->mNext = listElem; Traits::enterList(aElem); } /* * Insert aElem before this element, but don't check that this element is in * the list. This is called by LinkedList::insertBack(). */ void setPreviousUnsafe(RawType aElem) { LinkedListElement* listElem = static_cast*>(aElem); MOZ_ASSERT(!listElem->isInList()); listElem->mNext = this; listElem->mPrev = this->mPrev; this->mPrev->mNext = listElem; this->mPrev = listElem; Traits::enterList(aElem); } /* * Adjust mNext and mPrev for implementing move constructor and move * assignment. */ void adjustLinkForMove(LinkedListElement&& aOther) { if (!aOther.isInList()) { mNext = this; mPrev = this; return; } if (!mIsSentinel) { Traits::enterList(this); } MOZ_ASSERT(aOther.mNext->mPrev == &aOther); MOZ_ASSERT(aOther.mPrev->mNext == &aOther); /* * Initialize |this| with |aOther|'s mPrev/mNext pointers, and adjust those * element to point to this one. */ mNext = aOther.mNext; mPrev = aOther.mPrev; mNext->mPrev = this; mPrev->mNext = this; /* * Adjust |aOther| so it doesn't think it's in a list. This makes it * safely destructable. */ aOther.mNext = &aOther; aOther.mPrev = &aOther; if (!mIsSentinel) { Traits::exitList(&aOther); } } LinkedListElement& operator=(const LinkedListElement& aOther) = delete; LinkedListElement(const LinkedListElement& aOther) = delete; }; template class LinkedList { private: typedef typename detail::LinkedListElementTraits Traits; typedef typename Traits::RawType RawType; typedef typename Traits::ConstRawType ConstRawType; typedef typename Traits::ClientType ClientType; typedef typename Traits::ConstClientType ConstClientType; typedef LinkedListElement* ElementType; typedef const LinkedListElement* ConstElementType; LinkedListElement sentinel; public: template class Iterator { Type mCurrent; public: using iterator_category = std::forward_iterator_tag; using value_type = T; using difference_type = std::ptrdiff_t; using pointer = T*; using reference = T&; explicit Iterator(Type aCurrent) : mCurrent(aCurrent) {} Type operator*() const { return mCurrent; } const Iterator& operator++() { mCurrent = static_cast(mCurrent)->getNext(); return *this; } bool operator!=(const Iterator& aOther) const { return mCurrent != aOther.mCurrent; } }; LinkedList() : sentinel(LinkedListElement::NodeKind::Sentinel) {} LinkedList(LinkedList&& aOther) : sentinel(std::move(aOther.sentinel)) {} LinkedList& operator=(LinkedList&& aOther) { MOZ_ASSERT(isEmpty(), "Assigning to a non-empty list leaks elements in that list!"); sentinel = std::move(aOther.sentinel); return *this; } ~LinkedList() { # ifdef DEBUG if (!isEmpty()) { MOZ_CRASH_UNSAFE_PRINTF( "%s has a buggy user: " "it should have removed all this list's elements before " "the list's destruction", __PRETTY_FUNCTION__); } # endif } /* * Add aElem to the front of the list. */ void insertFront(RawType aElem) { /* Bypass setNext()'s this->isInList() assertion. */ sentinel.setNextUnsafe(aElem); } /* * Add aElem to the back of the list. */ void insertBack(RawType aElem) { sentinel.setPreviousUnsafe(aElem); } /* * Get the first element of the list, or nullptr if the list is empty. */ RawType getFirst() { return sentinel.getNext(); } ConstRawType getFirst() const { return sentinel.getNext(); } /* * Get the last element of the list, or nullptr if the list is empty. */ RawType getLast() { return sentinel.getPrevious(); } ConstRawType getLast() const { return sentinel.getPrevious(); } /* * Get and remove the first element of the list. If the list is empty, * return nullptr. */ ClientType popFirst() { ClientType ret = sentinel.getNext(); if (ret) { static_cast*>(RawType(ret))->remove(); } return ret; } /* * Get and remove the last element of the list. If the list is empty, * return nullptr. */ ClientType popLast() { ClientType ret = sentinel.getPrevious(); if (ret) { static_cast*>(RawType(ret))->remove(); } return ret; } /* * Return true if the list is empty, or false otherwise. */ bool isEmpty() const { return !sentinel.isInList(); } /** * Returns whether the given element is in the list. */ bool contains(ConstRawType aElm) const { return std::find(begin(), end(), aElm) != end(); } /* * Remove all the elements from the list. * * This runs in time linear to the list's length, because we have to mark * each element as not in the list. */ void clear() { while (popFirst()) { } } /** * Return the length of elements in the list. */ size_t length() const { return std::distance(begin(), end()); } /* * Allow range-based iteration: * * for (MyElementType* elt : myList) { ... } */ Iterator begin() { return Iterator(getFirst()); } Iterator begin() const { return Iterator(getFirst()); } Iterator end() { return Iterator(nullptr); } Iterator end() const { return Iterator(nullptr); } /* * Measures the memory consumption of the list excluding |this|. Note that * it only measures the list elements themselves. If the list elements * contain pointers to other memory blocks, those blocks must be measured * separately during a subsequent iteration over the list. */ size_t sizeOfExcludingThis(MallocSizeOf aMallocSizeOf) const { size_t n = 0; ConstRawType t = getFirst(); while (t) { n += aMallocSizeOf(t); t = static_cast*>(t)->getNext(); } return n; } /* * Like sizeOfExcludingThis(), but measures |this| as well. */ size_t sizeOfIncludingThis(MallocSizeOf aMallocSizeOf) const { return aMallocSizeOf(this) + sizeOfExcludingThis(aMallocSizeOf); } /* * In a debug build, make sure that the list is sane (no cycles, consistent * mNext/mPrev pointers, only one sentinel). Has no effect in release builds. */ void debugAssertIsSane() const { # ifdef DEBUG const LinkedListElement* slow; const LinkedListElement* fast1; const LinkedListElement* fast2; /* * Check for cycles in the forward singly-linked list using the * tortoise/hare algorithm. */ for (slow = sentinel.mNext, fast1 = sentinel.mNext->mNext, fast2 = sentinel.mNext->mNext->mNext; slow != &sentinel && fast1 != &sentinel && fast2 != &sentinel; slow = slow->mNext, fast1 = fast2->mNext, fast2 = fast1->mNext) { MOZ_ASSERT(slow != fast1); MOZ_ASSERT(slow != fast2); } /* Check for cycles in the backward singly-linked list. */ for (slow = sentinel.mPrev, fast1 = sentinel.mPrev->mPrev, fast2 = sentinel.mPrev->mPrev->mPrev; slow != &sentinel && fast1 != &sentinel && fast2 != &sentinel; slow = slow->mPrev, fast1 = fast2->mPrev, fast2 = fast1->mPrev) { MOZ_ASSERT(slow != fast1); MOZ_ASSERT(slow != fast2); } /* * Check that |sentinel| is the only node in the list with * mIsSentinel == true. */ for (const LinkedListElement* elem = sentinel.mNext; elem != &sentinel; elem = elem->mNext) { MOZ_ASSERT(!elem->mIsSentinel); } /* Check that the mNext/mPrev pointers match up. */ const LinkedListElement* prev = &sentinel; const LinkedListElement* cur = sentinel.mNext; do { MOZ_ASSERT(cur->mPrev == prev); MOZ_ASSERT(prev->mNext == cur); prev = cur; cur = cur->mNext; } while (cur != &sentinel); # endif /* ifdef DEBUG */ } private: friend class LinkedListElement; void assertContains(const RawType aValue) const { # ifdef DEBUG for (ConstRawType elem = getFirst(); elem; elem = elem->getNext()) { if (elem == aValue) { return; } } MOZ_CRASH("element wasn't found in this list!"); # endif } LinkedList& operator=(const LinkedList& aOther) = delete; LinkedList(const LinkedList& aOther) = delete; }; template class AutoCleanLinkedList : public LinkedList { private: using Traits = detail::LinkedListElementTraits; using ClientType = typename detail::LinkedListElementTraits::ClientType; public: ~AutoCleanLinkedList() { clear(); } AutoCleanLinkedList& operator=(AutoCleanLinkedList&& aOther) = default; void clear() { while (ClientType element = this->popFirst()) { Traits::cleanElement(element); } } }; } /* namespace mozilla */ #endif /* __cplusplus */ #endif /* mozilla_LinkedList_h */