1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /* vim: set ts=8 sts=2 et sw=2 tw=80: */
3 /* This Source Code Form is subject to the terms of the Mozilla Public
4 * License, v. 2.0. If a copy of the MPL was not distributed with this
5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
7 /** A doubly-linked list with flexible next/prev naming. */
9 #ifndef mozilla_DoublyLinkedList_h
10 #define mozilla_DoublyLinkedList_h
15 #include <type_traits>
17 #include "mozilla/Assertions.h"
20 * Where mozilla::LinkedList strives for ease of use above all other
21 * considerations, mozilla::DoublyLinkedList strives for flexibility. The
22 * following are things that can be done with mozilla::DoublyLinkedList that
23 * cannot be done with mozilla::LinkedList:
25 * * Arbitrary next/prev placement and naming. With the tools provided here,
26 * the next and previous pointers can be at the end of the structure, in a
27 * sub-structure, stored with a tag, in a union, wherever, as long as you
28 * can look them up and set them on demand.
29 * * Can be used without deriving from a new base and, thus, does not require
30 * use of constructors.
34 * class Observer : public DoublyLinkedListElement<Observer>
37 * void observe(char* aTopic) { ... }
40 * class ObserverContainer
43 * DoublyLinkedList<Observer> mList;
46 * void addObserver(Observer* aObserver)
48 * // Will assert if |aObserver| is part of another list.
49 * mList.pushBack(aObserver);
52 * void removeObserver(Observer* aObserver)
54 * // Will assert if |aObserver| is not part of |list|.
55 * mList.remove(aObserver);
58 * void notifyObservers(char* aTopic)
60 * for (Observer* o : mList) {
70 * Deriving from this will allow T to be inserted into and removed from a
74 class DoublyLinkedListElement
{
75 template <typename U
, typename E
>
76 friend class DoublyLinkedList
;
82 DoublyLinkedListElement() : mNext(nullptr), mPrev(nullptr) {}
86 * Provides access to a DoublyLinkedListElement within T.
88 * The default implementation of this template works for types that derive
89 * from DoublyLinkedListElement, but one can specialize for their class so
90 * that some appropriate DoublyLinkedListElement reference is returned.
92 * For more complex cases (multiple DoublyLinkedListElements, for example),
93 * one can define their own trait class and use that as ElementAccess for
94 * DoublyLinkedList. See TestDoublyLinkedList.cpp for an example.
97 struct GetDoublyLinkedListElement
{
98 static_assert(std::is_base_of
<DoublyLinkedListElement
<T
>, T
>::value
,
99 "You need your own specialization of GetDoublyLinkedListElement"
100 " or use a separate Trait.");
101 static DoublyLinkedListElement
<T
>& Get(T
* aThis
) { return *aThis
; }
105 * A doubly linked list. |T| is the type of element stored in this list. |T|
106 * must contain or have access to unique next and previous element pointers.
107 * The template argument |ElementAccess| provides code to tell this list how to
108 * get a reference to a DoublyLinkedListElement that may reside anywhere.
110 template <typename T
, typename ElementAccess
= GetDoublyLinkedListElement
<T
>>
111 class DoublyLinkedList final
{
116 * Checks that either the list is empty and both mHead and mTail are nullptr
117 * or the list has entries and both mHead and mTail are non-null.
119 bool isStateValid() const { return (mHead
!= nullptr) == (mTail
!= nullptr); }
121 bool ElementNotInList(T
* aElm
) {
122 if (!ElementAccess::Get(aElm
).mNext
&& !ElementAccess::Get(aElm
).mPrev
) {
123 // Both mNext and mPrev being NULL can mean two things:
124 // - the element is not in the list.
125 // - the element is the first and only element in the list.
126 // So check for the latter.
127 return mHead
!= aElm
;
133 DoublyLinkedList() : mHead(nullptr), mTail(nullptr) {}
135 class Iterator final
{
139 using iterator_category
= std::forward_iterator_tag
;
140 using value_type
= T
;
141 using difference_type
= std::ptrdiff_t;
143 using reference
= T
&;
145 Iterator() : mCurrent(nullptr) {}
146 explicit Iterator(T
* aCurrent
) : mCurrent(aCurrent
) {}
148 T
& operator*() const { return *mCurrent
; }
149 T
* operator->() const { return mCurrent
; }
151 Iterator
& operator++() {
152 mCurrent
= ElementAccess::Get(mCurrent
).mNext
;
156 Iterator
operator++(int) {
157 Iterator result
= *this;
162 Iterator
& operator--() {
163 mCurrent
= ElementAccess::Get(mCurrent
).mPrev
;
167 Iterator
operator--(int) {
168 Iterator result
= *this;
173 bool operator!=(const Iterator
& aOther
) const {
174 return mCurrent
!= aOther
.mCurrent
;
177 bool operator==(const Iterator
& aOther
) const {
178 return mCurrent
== aOther
.mCurrent
;
181 explicit operator bool() const { return mCurrent
; }
184 Iterator
begin() { return Iterator(mHead
); }
185 const Iterator
begin() const { return Iterator(mHead
); }
186 const Iterator
cbegin() const { return Iterator(mHead
); }
188 Iterator
end() { return Iterator(); }
189 const Iterator
end() const { return Iterator(); }
190 const Iterator
cend() const { return Iterator(); }
193 * Returns true if the list contains no elements.
195 bool isEmpty() const {
196 MOZ_ASSERT(isStateValid());
197 return mHead
== nullptr;
201 * Inserts aElm into the list at the head position. |aElm| must not already
204 void pushFront(T
* aElm
) {
206 MOZ_ASSERT(ElementNotInList(aElm
));
207 MOZ_ASSERT(isStateValid());
209 ElementAccess::Get(aElm
).mNext
= mHead
;
211 MOZ_ASSERT(!ElementAccess::Get(mHead
).mPrev
);
212 ElementAccess::Get(mHead
).mPrev
= aElm
;
222 * Remove the head of the list and return it. Calling this on an empty list
226 MOZ_ASSERT(!isEmpty());
227 MOZ_ASSERT(isStateValid());
230 mHead
= result
? ElementAccess::Get(result
).mNext
: nullptr;
232 ElementAccess::Get(mHead
).mPrev
= nullptr;
235 if (mTail
== result
) {
240 ElementAccess::Get(result
).mNext
= nullptr;
241 ElementAccess::Get(result
).mPrev
= nullptr;
248 * Inserts aElm into the list at the tail position. |aElm| must not already
251 void pushBack(T
* aElm
) {
253 MOZ_ASSERT(ElementNotInList(aElm
));
254 MOZ_ASSERT(isStateValid());
256 ElementAccess::Get(aElm
).mNext
= nullptr;
257 ElementAccess::Get(aElm
).mPrev
= mTail
;
259 MOZ_ASSERT(!ElementAccess::Get(mTail
).mNext
);
260 ElementAccess::Get(mTail
).mNext
= aElm
;
270 * Remove the tail of the list and return it. Calling this on an empty list
274 MOZ_ASSERT(!isEmpty());
275 MOZ_ASSERT(isStateValid());
278 mTail
= result
? ElementAccess::Get(result
).mPrev
: nullptr;
280 ElementAccess::Get(mTail
).mNext
= nullptr;
283 if (mHead
== result
) {
288 ElementAccess::Get(result
).mNext
= nullptr;
289 ElementAccess::Get(result
).mPrev
= nullptr;
296 * Insert the given |aElm| *before* |aIter|.
298 void insertBefore(const Iterator
& aIter
, T
* aElm
) {
300 MOZ_ASSERT(ElementNotInList(aElm
));
301 MOZ_ASSERT(isStateValid());
304 return pushBack(aElm
);
305 } else if (aIter
== begin()) {
306 return pushFront(aElm
);
309 T
* after
= &(*aIter
);
310 T
* before
= ElementAccess::Get(after
).mPrev
;
313 ElementAccess::Get(before
).mNext
= aElm
;
314 ElementAccess::Get(aElm
).mPrev
= before
;
315 ElementAccess::Get(aElm
).mNext
= after
;
316 ElementAccess::Get(after
).mPrev
= aElm
;
320 * Removes the given element from the list. The element must be in this list.
322 void remove(T
* aElm
) {
324 MOZ_ASSERT(ElementAccess::Get(aElm
).mNext
||
325 ElementAccess::Get(aElm
).mPrev
||
326 (aElm
== mHead
&& aElm
== mTail
),
327 "Attempted to remove element not in this list");
329 if (T
* prev
= ElementAccess::Get(aElm
).mPrev
) {
330 ElementAccess::Get(prev
).mNext
= ElementAccess::Get(aElm
).mNext
;
332 MOZ_ASSERT(mHead
== aElm
);
333 mHead
= ElementAccess::Get(aElm
).mNext
;
336 if (T
* next
= ElementAccess::Get(aElm
).mNext
) {
337 ElementAccess::Get(next
).mPrev
= ElementAccess::Get(aElm
).mPrev
;
339 MOZ_ASSERT(mTail
== aElm
);
340 mTail
= ElementAccess::Get(aElm
).mPrev
;
343 ElementAccess::Get(aElm
).mNext
= nullptr;
344 ElementAccess::Get(aElm
).mPrev
= nullptr;
348 * Returns an iterator referencing the first found element whose value matches
349 * the given element according to operator==.
351 Iterator
find(const T
& aElm
) { return std::find(begin(), end(), aElm
); }
354 * Returns whether the given element is in the list. Note that this uses
355 * T::operator==, not pointer comparison.
357 bool contains(const T
& aElm
) { return find(aElm
) != Iterator(); }
360 * Returns whether the given element might be in the list. Note that this
361 * assumes the element is either in the list or not in the list, and ignores
362 * the case where the element might be in another list in order to make the
365 bool ElementProbablyInList(T
* aElm
) {
369 return !ElementNotInList(aElm
);
373 } // namespace mozilla
375 #endif // mozilla_DoublyLinkedList_h