#ifndef __CLIST_H__ #define __CLIST_H__ // // TFList - a class for handling templated lists. Lists support adding // and removing items from the front and back of the list. The item on // the front or back can be examined. Using iterator the list can // be traversed easily (see the block comment below). // // The objects that this list hold should have this form: // // CListObject { // public: // CListObject *m_pNext; // for use by TFList // CListObject *m_pPrev; // for use by TFList // // other object parameters // } // // The constructor for the object should set m_pNext and m_pPrev to NULL // in debug builds to avoid hitting _ASSERTs. // // To construct the list you need to pass in the offsets for the m_pNext // and m_pPrev members using C++'s member offset syntax. Here is an // example: // TFList list(&CListObject::m_pPrev, &CListObject::m_pNext); // template class TFList { public: typedef Data *Data::*NEXTPTR; protected: Data *m_pHead; // the head of the list Data *m_pTail; // the tail of the list NEXTPTR m_pNext; // offset to the next pointer NEXTPTR m_pPrev; // offset to the prev pointer public: TFList(NEXTPTR pPrev, NEXTPTR pNext) { m_pHead = NULL; m_pTail = NULL; m_pNext = pNext; m_pPrev = pPrev; } ~TFList() { // the user should empty the list before deleting it _ASSERT(m_pHead == NULL); _ASSERT(m_pTail == NULL); } // see if the list is empty bool IsEmpty() { bool f = (m_pHead == NULL); // if the head is null then the tail has to be null _ASSERT(!f || m_pTail == NULL); return f; } // push an element onto the front of the list void PushFront(Data *node) { _ASSERT(node != NULL); // you can't put an entry into the list that is already in it _ASSERT(node->*m_pNext == NULL); _ASSERT(node->*m_pPrev == NULL); // set the next and prev pointers node->*m_pPrev = NULL; node->*m_pNext = m_pHead; // if the list is empty then this new item is the tail too if (IsEmpty()) { _ASSERT(m_pTail == NULL); m_pTail = node; } else { _ASSERT(m_pHead->*m_pPrev == NULL); m_pHead->*m_pPrev = node; } m_pHead = node; } // push an element onto the back of the list void PushBack(Data* node) { _ASSERT(node != NULL); // you can't put an entry into the list that is already in it _ASSERT(node->*m_pNext == NULL); _ASSERT(node->*m_pPrev == NULL); // set the next and prev pointers node->*m_pNext = NULL; node->*m_pPrev = m_pTail; // if the list is empty then this new item is the head too if (IsEmpty()) { _ASSERT(m_pHead == NULL); m_pHead = node; } else { _ASSERT(m_pTail->*m_pNext == NULL); m_pTail->*m_pNext = node; } m_pTail = node; } // remove the item from the front of the list Data *PopFront() { if (m_pHead == NULL) return NULL; Data *node = m_pHead; m_pHead = node->*m_pNext; if (m_pHead == NULL) m_pTail = NULL; else m_pHead->*m_pPrev = NULL; node->*m_pNext = NULL; node->*m_pPrev = NULL; return node; } // remove the item from the back of the list Data *PopBack() { if (m_pTail == NULL) return NULL; Data *node = m_pTail; m_pTail = node->*m_pPrev; if (m_pTail == NULL) m_pHead = NULL; else (m_pTail)->*m_pNext = NULL; node->*m_pNext = NULL; node->*m_pPrev = NULL; return node; } // get the item on the front of the list Data* GetFront() { return m_pHead; } // get the item on the back of the list Data* GetBack() { return m_pTail; } public: // // The Iterator object is used to walk the list and modify members // that are in the middle of the list. It is declared using this // syntax: // TFList::Iterator it(&list); // class Iterator { protected: Data *m_pCur; // our cursor int m_fForward; // TRUE for forward, FALSE for back TFList *m_pList; // the list that we are iterating NEXTPTR m_pPrev, m_pNext; public: // // create a new iterator object // // arguments: // pList - the list to iterate across // fForward - TRUE to start at the front, and go forward. // FALSE to start at the back, and go backwards. // Iterator(TFList *pList, BOOL fForward = TRUE) { _ASSERT(pList != NULL); m_pList = pList; m_fForward = fForward; m_pCur = (fForward) ? pList->m_pHead : pList->m_pTail; m_pPrev = pList->m_pPrev; m_pNext = pList->m_pNext; } void ResetHeader( TFList *pList ) { _ASSERT( pList != NULL ); m_pList = pList; m_pCur = (m_fForward) ? m_pList->m_pHead : m_pList->m_pTail; m_pPrev = m_pList->m_pPrev; m_pNext = m_pList->m_pNext; } // // get a pointer to the current item // Data *Current() { return m_pCur; } // // go to the previous item in the list // void Prev() { if (m_pCur != NULL) { m_pCur = m_pCur->*m_pPrev; } else { // if they switch direction and are at the end of // the list then they need to get to a legal place if (m_fForward) m_pCur = m_pList->m_pTail; } m_fForward = FALSE; } // // go to the next item in the list // void Next() { if (m_pCur != NULL) { m_pCur = m_pCur->*m_pNext; } else { // if they switch direction and are at the end of // the list then they need to get to a legal place if (!m_fForward) m_pCur = m_pList->m_pHead; } m_fForward = TRUE; } // // Go to the head of the list // void Front() { m_pCur = m_pList->m_pHead; m_fForward = TRUE; } // // Go to the tail of the list // void Back() { m_pCur = m_pList->m_pTail; m_fForward = FALSE; } // // unlinks an item from the linked list // // cursor updates: // if the last movement in this list was forward then the // iterator will point towards the pPrev item in the list. // visa-versa if the last movement was backward. this is // so that a for loop over an iterator still works as // expected. // // returns: // a pointer to the item that was unlinked. // Data *RemoveItem(void) { Data *pTemp; if (m_pCur == NULL) return NULL; pTemp = m_pCur; // update cur if (m_fForward) Next(); else Prev(); // fix head and tail pointers if necessary if (m_pList->m_pHead == pTemp) m_pList->m_pHead = pTemp->*m_pNext; if (m_pList->m_pTail == pTemp) m_pList->m_pTail = pTemp->*m_pPrev; // fix up the links on the adjacent elements if (pTemp->*m_pNext != NULL) pTemp->*m_pNext->*m_pPrev = pTemp->*m_pPrev; if (pTemp->*m_pPrev != NULL) pTemp->*m_pPrev->*m_pNext = pTemp->*m_pNext; // clean up the next and prev pointers pTemp->*m_pNext = NULL; pTemp->*m_pPrev = NULL; // return the item return pTemp; } // // insert a new item before the current item. // // Cursor updates: // If you use this method to insert an item then it should // not be visited if the next cursor movement is a Next(). // If the next cursor movement is a Prev() then it should // be visited. // void InsertBefore(Data* pNode) { // this entry shouldn't be linked into the list _ASSERT(pNode->*m_pNext == NULL); _ASSERT(pNode->*m_pPrev == NULL); if (m_pCur == NULL) { // if we are at the head of the list then we'll insert // before the head if (m_fForward) { m_pList->PushFront(pNode); // set the current pointer to this item, so that // if we iterate forward we dont' see this item m_pCur = pNode; } else { // invalid operation. do nothing _ASSERT(FALSE); } } else { pNode->*m_pNext = m_pCur; pNode->*m_pPrev = m_pCur->*m_pPrev; m_pCur->*m_pPrev = pNode; if (pNode->*m_pPrev != NULL) pNode->*m_pPrev->*m_pNext = pNode; if (m_pList->m_pHead == m_pCur) m_pList->m_pHead = pNode; } } // // insert a new item after the current item. // // Cursor updates are the opposite of InsertBefore(). // void InsertAfter(Data *pNode) { // this entry shouldn't be linked into the list _ASSERT(pNode->*m_pNext == NULL); _ASSERT(pNode->*m_pPrev == NULL); if (m_pCur == NULL) { // if we are at the tail of the list then we'll insert // before the tail if (!m_fForward) { m_pList->PushBack(pNode); // set the current pointer to this item, so that // if we iterate backwards we dont' see this item m_pCur = pNode; } else { // invalid operation. do nothing _ASSERT(FALSE); } } else { pNode->*m_pPrev = m_pCur; pNode->*m_pNext = m_pCur->*m_pNext; m_pCur->*m_pNext = pNode; if (pNode->*m_pNext != NULL) pNode->*m_pNext->*m_pPrev = pNode; if (m_pList->m_pTail == m_pCur) m_pList->m_pTail = pNode; } } // // see if we are at either the front or back of the list // bool AtEnd() { return (m_pCur == NULL); } }; friend class TFList::Iterator; }; #endif