blob: 5cfd60e4ac00a9665692ae764d0ed1b3c261d8bf [file] [log] [blame]
/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* 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<T> and LinkedListElement<T> 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<T>. 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<Observer>
* {
* public:
* void observe(char* topic) { ... }
* };
*
* class ObserverContainer
* {
* private:
* LinkedList<Observer> list;
*
* public:
* void addObserver(Observer* observer) {
* // Will assert if |observer| is part of another list.
* list.insertBack(observer);
* }
*
* void removeObserver(Observer* observer) {
* // Will assert if |observer| is not part of some list.
* observer.remove();
* // Or, will assert if |observer| is not part of |list| specifically.
* // observer.removeFrom(list);
* }
*
* void notifyObservers(char* topic) {
* for (Observer* o = list.getFirst(); o != NULL; o = o->getNext())
* o->Observe(topic);
* }
* };
*
*/
#ifndef mozilla_LinkedList_h_
#define mozilla_LinkedList_h_
#include "mozilla/Assertions.h"
#include "mozilla/Attributes.h"
#ifdef __cplusplus
namespace mozilla {
template<typename T>
class LinkedList;
template<typename T>
class LinkedListElement
{
/*
* It's convenient that we return NULL 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 prev/next pointers in the object itself,
* while stl::list stores the prev/next 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* next;
LinkedListElement* prev;
const bool isSentinel;
LinkedListElement* thisDuringConstruction() { return this; }
public:
LinkedListElement()
: next(thisDuringConstruction()),
prev(thisDuringConstruction()),
isSentinel(false)
{ }
~LinkedListElement() {
if (!isSentinel && isInList())
remove();
}
/*
* Get the next element in the list, or NULL if this is the last element in
* the list.
*/
T* getNext() {
return next->asT();
}
const T* getNext() const {
return next->asT();
}
/*
* Get the previous element in the list, or NULL if this is the first element
* in the list.
*/
T* getPrevious() {
return prev->asT();
}
const T* getPrevious() const {
return prev->asT();
}
/*
* Insert elem 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(T* elem) {
MOZ_ASSERT(isInList());
setNextUnsafe(elem);
}
/*
* Insert elem 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(T* elem) {
MOZ_ASSERT(isInList());
setPreviousUnsafe(elem);
}
/*
* 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());
prev->next = next;
next->prev = prev;
next = this;
prev = this;
}
/*
* Identical to remove(), but also asserts in debug builds that this element
* is in list.
*/
void removeFrom(const LinkedList<T>& list) {
list.assertContains(asT());
remove();
}
/*
* Return true if |this| part is of a linked list, and false otherwise.
*/
bool isInList() const {
MOZ_ASSERT((next == this) == (prev == this));
return next != this;
}
private:
friend class LinkedList<T>;
enum NodeKind {
NODE_KIND_NORMAL,
NODE_KIND_SENTINEL
};
LinkedListElement(NodeKind nodeKind)
: next(thisDuringConstruction()),
prev(thisDuringConstruction()),
isSentinel(nodeKind == NODE_KIND_SENTINEL)
{ }
/*
* Return |this| cast to T* if we're a normal node, or return NULL if we're
* a sentinel node.
*/
T* asT() {
if (isSentinel)
return NULL;
return static_cast<T*>(this);
}
const T* asT() const {
if (isSentinel)
return NULL;
return static_cast<const T*>(this);
}
/*
* Insert elem after this element, but don't check that this element is in
* the list. This is called by LinkedList::insertFront().
*/
void setNextUnsafe(T* elem) {
LinkedListElement *listElem = static_cast<LinkedListElement*>(elem);
MOZ_ASSERT(!listElem->isInList());
listElem->next = this->next;
listElem->prev = this;
this->next->prev = listElem;
this->next = listElem;
}
/*
* Insert elem before this element, but don't check that this element is in
* the list. This is called by LinkedList::insertBack().
*/
void setPreviousUnsafe(T* elem) {
LinkedListElement<T>* listElem = static_cast<LinkedListElement<T>*>(elem);
MOZ_ASSERT(!listElem->isInList());
listElem->next = this;
listElem->prev = this->prev;
this->prev->next = listElem;
this->prev = listElem;
}
private:
LinkedListElement& operator=(const LinkedList<T>& other) MOZ_DELETE;
LinkedListElement(const LinkedList<T>& other) MOZ_DELETE;
};
template<typename T>
class LinkedList
{
private:
LinkedListElement<T> sentinel;
public:
LinkedList() : sentinel(LinkedListElement<T>::NODE_KIND_SENTINEL) { }
~LinkedList() {
MOZ_ASSERT(isEmpty());
}
/*
* Add elem to the front of the list.
*/
void insertFront(T* elem) {
/* Bypass setNext()'s this->isInList() assertion. */
sentinel.setNextUnsafe(elem);
}
/*
* Add elem to the back of the list.
*/
void insertBack(T* elem) {
sentinel.setPreviousUnsafe(elem);
}
/*
* Get the first element of the list, or NULL if the list is empty.
*/
T* getFirst() {
return sentinel.getNext();
}
const T* getFirst() const {
return sentinel.getNext();
}
/*
* Get the last element of the list, or NULL if the list is empty.
*/
T* getLast() {
return sentinel.getPrevious();
}
const T* getLast() const {
return sentinel.getPrevious();
}
/*
* Get and remove the first element of the list. If the list is empty,
* return NULL.
*/
T* popFirst() {
T* ret = sentinel.getNext();
if (ret)
static_cast<LinkedListElement<T>*>(ret)->remove();
return ret;
}
/*
* Get and remove the last element of the list. If the list is empty,
* return NULL.
*/
T* popLast() {
T* ret = sentinel.getPrevious();
if (ret)
static_cast<LinkedListElement<T>*>(ret)->remove();
return ret;
}
/*
* Return true if the list is empty, or false otherwise.
*/
bool isEmpty() const {
return !sentinel.isInList();
}
/*
* 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())
continue;
}
/*
* In a debug build, make sure that the list is sane (no cycles, consistent
* next/prev pointers, only one sentinel). Has no effect in release builds.
*/
void debugAssertIsSane() const {
#ifdef DEBUG
const LinkedListElement<T>* slow;
const LinkedListElement<T>* fast1;
const LinkedListElement<T>* fast2;
/*
* Check for cycles in the forward singly-linked list using the
* tortoise/hare algorithm.
*/
for (slow = sentinel.next,
fast1 = sentinel.next->next,
fast2 = sentinel.next->next->next;
slow != sentinel && fast1 != sentinel && fast2 != sentinel;
slow = slow->next, fast1 = fast2->next, fast2 = fast1->next)
{
MOZ_ASSERT(slow != fast1);
MOZ_ASSERT(slow != fast2);
}
/* Check for cycles in the backward singly-linked list. */
for (slow = sentinel.prev,
fast1 = sentinel.prev->prev,
fast2 = sentinel.prev->prev->prev;
slow != sentinel && fast1 != sentinel && fast2 != sentinel;
slow = slow->prev, fast1 = fast2->prev, fast2 = fast1->prev)
{
MOZ_ASSERT(slow != fast1);
MOZ_ASSERT(slow != fast2);
}
/*
* Check that |sentinel| is the only node in the list with
* isSentinel == true.
*/
for (const LinkedListElement<T>* elem = sentinel.next;
elem != sentinel;
elem = elem->next)
{
MOZ_ASSERT(!elem->isSentinel);
}
/* Check that the next/prev pointers match up. */
const LinkedListElement<T>* prev = sentinel;
const LinkedListElement<T>* cur = sentinel.next;
do {
MOZ_ASSERT(cur->prev == prev);
MOZ_ASSERT(prev->next == cur);
prev = cur;
cur = cur->next;
} while (cur != sentinel);
#endif /* ifdef DEBUG */
}
private:
friend class LinkedListElement<T>;
void assertContains(const T* t) const {
#ifdef DEBUG
for (const T* elem = getFirst();
elem;
elem = elem->getNext())
{
if (elem == t)
return;
}
MOZ_NOT_REACHED("element wasn't found in this list!");
#endif
}
LinkedList& operator=(const LinkedList<T>& other) MOZ_DELETE;
LinkedList(const LinkedList<T>& other) MOZ_DELETE;
};
} /* namespace mozilla */
#endif /* ifdef __cplusplus */
#endif /* ifdef mozilla_LinkedList_h_ */