| //------------------------------------------------------------------------------ | |
| // File: WXList.cpp | |
| // | |
| // Desc: DirectShow base classes - implements a non-MFC based generic list | |
| // template class. | |
| // Copyright (c) 1992-2001 Microsoft Corporation. All rights reserved. | |
| //------------------------------------------------------------------------------ | |
| /* A generic list of pointers to objects. | |
| Objectives: avoid using MFC libraries in ndm kernel mode and | |
| provide a really useful list type. | |
| The class is thread safe in that separate threads may add and | |
| delete items in the list concurrently although the application | |
| must ensure that constructor and destructor access is suitably | |
| synchronised. | |
| The list name must not conflict with MFC classes as an | |
| application may use both | |
| The nodes form a doubly linked, NULL terminated chain with an anchor | |
| block (the list object per se) holding pointers to the first and last | |
| nodes and a count of the nodes. | |
| There is a node cache to reduce the allocation and freeing overhead. | |
| It optionally (determined at construction time) has an Event which is | |
| set whenever the list becomes non-empty and reset whenever it becomes | |
| empty. | |
| It optionally (determined at construction time) has a Critical Section | |
| which is entered during the important part of each operation. (About | |
| all you can do outside it is some parameter checking). | |
| The node cache is a repository of nodes that are NOT in the list to speed | |
| up storage allocation. Each list has its own cache to reduce locking and | |
| serialising. The list accesses are serialised anyway for a given list - a | |
| common cache would mean that we would have to separately serialise access | |
| of all lists within the cache. Because the cache only stores nodes that are | |
| not in the list, releasing the cache does not release any list nodes. This | |
| means that list nodes can be copied or rechained from one list to another | |
| without danger of creating a dangling reference if the original cache goes | |
| away. | |
| Questionable design decisions: | |
| 1. Retaining the warts for compatibility | |
| 2. Keeping an element count -i.e. counting whenever we do anything | |
| instead of only when we want the count. | |
| 3. Making the chain pointers NULL terminated. If the list object | |
| itself looks just like a node and the list is kept as a ring then | |
| it reduces the number of special cases. All inserts look the same. | |
| */ | |
| #include <streams.h> | |
| /* set cursor to the position of each element of list in turn */ | |
| #define INTERNALTRAVERSELIST(list, cursor) \ | |
| for ( cursor = (list).GetHeadPositionI() \ | |
| ; cursor!=NULL \ | |
| ; cursor = (list).Next(cursor) \ | |
| ) | |
| /* set cursor to the position of each element of list in turn | |
| in reverse order | |
| */ | |
| #define INTERNALREVERSETRAVERSELIST(list, cursor) \ | |
| for ( cursor = (list).GetTailPositionI() \ | |
| ; cursor!=NULL \ | |
| ; cursor = (list).Prev(cursor) \ | |
| ) | |
| /* Constructor calls a separate initialisation function that | |
| creates a node cache, optionally creates a lock object | |
| and optionally creates a signaling object. | |
| By default we create a locking object, a DEFAULTCACHE sized | |
| cache but no event object so the list cannot be used in calls | |
| to WaitForSingleObject | |
| */ | |
| CBaseList::CBaseList(__in_opt LPCTSTR pName, // Descriptive list name | |
| INT iItems) : // Node cache size | |
| #ifdef DEBUG | |
| CBaseObject(pName), | |
| #endif | |
| m_pFirst(NULL), | |
| m_pLast(NULL), | |
| m_Count(0), | |
| m_Cache(iItems) | |
| { | |
| } // constructor | |
| CBaseList::CBaseList(__in_opt LPCTSTR pName) : // Descriptive list name | |
| #ifdef DEBUG | |
| CBaseObject(pName), | |
| #endif | |
| m_pFirst(NULL), | |
| m_pLast(NULL), | |
| m_Count(0), | |
| m_Cache(DEFAULTCACHE) | |
| { | |
| } // constructor | |
| #ifdef UNICODE | |
| CBaseList::CBaseList(__in_opt LPCSTR pName, // Descriptive list name | |
| INT iItems) : // Node cache size | |
| #ifdef DEBUG | |
| CBaseObject(pName), | |
| #endif | |
| m_pFirst(NULL), | |
| m_pLast(NULL), | |
| m_Count(0), | |
| m_Cache(iItems) | |
| { | |
| } // constructor | |
| CBaseList::CBaseList(__in_opt LPCSTR pName) : // Descriptive list name | |
| #ifdef DEBUG | |
| CBaseObject(pName), | |
| #endif | |
| m_pFirst(NULL), | |
| m_pLast(NULL), | |
| m_Count(0), | |
| m_Cache(DEFAULTCACHE) | |
| { | |
| } // constructor | |
| #endif | |
| /* The destructor enumerates all the node objects in the list and | |
| in the cache deleting each in turn. We do not do any processing | |
| on the objects that the list holds (i.e. points to) so if they | |
| represent interfaces for example the creator of the list should | |
| ensure that each of them is released before deleting us | |
| */ | |
| CBaseList::~CBaseList() | |
| { | |
| /* Delete all our list nodes */ | |
| RemoveAll(); | |
| } // destructor | |
| /* Remove all the nodes from the list but don't do anything | |
| with the objects that each node looks after (this is the | |
| responsibility of the creator). | |
| Aa a last act we reset the signalling event | |
| (if available) to indicate to clients that the list | |
| does not have any entries in it. | |
| */ | |
| void CBaseList::RemoveAll() | |
| { | |
| /* Free up all the CNode objects NOTE we don't bother putting the | |
| deleted nodes into the cache as this method is only really called | |
| in serious times of change such as when we are being deleted at | |
| which point the cache will be deleted anway */ | |
| CNode *pn = m_pFirst; | |
| while (pn) { | |
| CNode *op = pn; | |
| pn = pn->Next(); | |
| delete op; | |
| } | |
| /* Reset the object count and the list pointers */ | |
| m_Count = 0; | |
| m_pFirst = m_pLast = NULL; | |
| } // RemoveAll | |
| /* Return a position enumerator for the entire list. | |
| A position enumerator is a pointer to a node object cast to a | |
| transparent type so all we do is return the head/tail node | |
| pointer in the list. | |
| WARNING because the position is a pointer to a node there is | |
| an implicit assumption for users a the list class that after | |
| deleting an object from the list that any other position | |
| enumerators that you have may be invalid (since the node | |
| may be gone). | |
| */ | |
| __out_opt POSITION CBaseList::GetHeadPositionI() const | |
| { | |
| return (POSITION) m_pFirst; | |
| } // GetHeadPosition | |
| __out_opt POSITION CBaseList::GetTailPositionI() const | |
| { | |
| return (POSITION) m_pLast; | |
| } // GetTailPosition | |
| /* Get the number of objects in the list, | |
| Get the lock before accessing the count. | |
| Locking may not be entirely necessary but it has the side effect | |
| of making sure that all operations are complete before we get it. | |
| So for example if a list is being added to this list then that | |
| will have completed in full before we continue rather than seeing | |
| an intermediate albeit valid state | |
| */ | |
| int CBaseList::GetCountI() const | |
| { | |
| return m_Count; | |
| } // GetCount | |
| /* Return the object at rp, update rp to the next object from | |
| the list or NULL if you have moved over the last object. | |
| You may still call this function once we return NULL but | |
| we will continue to return a NULL position value | |
| */ | |
| __out void *CBaseList::GetNextI(__inout POSITION& rp) const | |
| { | |
| /* have we reached the end of the list */ | |
| if (rp == NULL) { | |
| return NULL; | |
| } | |
| /* Lock the object before continuing */ | |
| void *pObject; | |
| /* Copy the original position then step on */ | |
| CNode *pn = (CNode *) rp; | |
| ASSERT(pn != NULL); | |
| rp = (POSITION) pn->Next(); | |
| /* Get the object at the original position from the list */ | |
| pObject = pn->GetData(); | |
| // ASSERT(pObject != NULL); // NULL pointers in the list are allowed. | |
| return pObject; | |
| } //GetNext | |
| /* Return the object at p. | |
| Asking for the object at NULL ASSERTs then returns NULL | |
| The object is NOT locked. The list is not being changed | |
| in any way. If another thread is busy deleting the object | |
| then locking would only result in a change from one bad | |
| behaviour to another. | |
| */ | |
| __out_opt void *CBaseList::GetI(__in_opt POSITION p) const | |
| { | |
| if (p == NULL) { | |
| return NULL; | |
| } | |
| CNode * pn = (CNode *) p; | |
| void *pObject = pn->GetData(); | |
| // ASSERT(pObject != NULL); // NULL pointers in the list are allowed. | |
| return pObject; | |
| } //Get | |
| __out void *CBaseList::GetValidI(__in POSITION p) const | |
| { | |
| CNode * pn = (CNode *) p; | |
| void *pObject = pn->GetData(); | |
| // ASSERT(pObject != NULL); // NULL pointers in the list are allowed. | |
| return pObject; | |
| } //Get | |
| /* Return the first position in the list which holds the given pointer. | |
| Return NULL if it's not found. | |
| */ | |
| __out_opt POSITION CBaseList::FindI( __in void * pObj) const | |
| { | |
| POSITION pn; | |
| INTERNALTRAVERSELIST(*this, pn){ | |
| if (GetI(pn)==pObj) { | |
| return pn; | |
| } | |
| } | |
| return NULL; | |
| } // Find | |
| /* Remove the first node in the list (deletes the pointer to its object | |
| from the list, does not free the object itself). | |
| Return the pointer to its object or NULL if empty | |
| */ | |
| __out_opt void *CBaseList::RemoveHeadI() | |
| { | |
| /* All we do is get the head position and ask for that to be deleted. | |
| We could special case this since some of the code path checking | |
| in Remove() is redundant as we know there is no previous | |
| node for example but it seems to gain little over the | |
| added complexity | |
| */ | |
| return RemoveI((POSITION)m_pFirst); | |
| } // RemoveHead | |
| /* Remove the last node in the list (deletes the pointer to its object | |
| from the list, does not free the object itself). | |
| Return the pointer to its object or NULL if empty | |
| */ | |
| __out_opt void *CBaseList::RemoveTailI() | |
| { | |
| /* All we do is get the tail position and ask for that to be deleted. | |
| We could special case this since some of the code path checking | |
| in Remove() is redundant as we know there is no previous | |
| node for example but it seems to gain little over the | |
| added complexity | |
| */ | |
| return RemoveI((POSITION)m_pLast); | |
| } // RemoveTail | |
| /* Remove the pointer to the object in this position from the list. | |
| Deal with all the chain pointers | |
| Return a pointer to the object removed from the list. | |
| The node object that is freed as a result | |
| of this operation is added to the node cache where | |
| it can be used again. | |
| Remove(NULL) is a harmless no-op - but probably is a wart. | |
| */ | |
| __out_opt void *CBaseList::RemoveI(__in_opt POSITION pos) | |
| { | |
| /* Lock the critical section before continuing */ | |
| // ASSERT (pos!=NULL); // Removing NULL is to be harmless! | |
| if (pos==NULL) return NULL; | |
| CNode *pCurrent = (CNode *) pos; | |
| ASSERT(pCurrent != NULL); | |
| /* Update the previous node */ | |
| CNode *pNode = pCurrent->Prev(); | |
| if (pNode == NULL) { | |
| m_pFirst = pCurrent->Next(); | |
| } else { | |
| pNode->SetNext(pCurrent->Next()); | |
| } | |
| /* Update the following node */ | |
| pNode = pCurrent->Next(); | |
| if (pNode == NULL) { | |
| m_pLast = pCurrent->Prev(); | |
| } else { | |
| pNode->SetPrev(pCurrent->Prev()); | |
| } | |
| /* Get the object this node was looking after */ | |
| void *pObject = pCurrent->GetData(); | |
| // ASSERT(pObject != NULL); // NULL pointers in the list are allowed. | |
| /* Try and add the node object to the cache - | |
| a NULL return code from the cache means we ran out of room. | |
| The cache size is fixed by a constructor argument when the | |
| list is created and defaults to DEFAULTCACHE. | |
| This means that the cache will have room for this many | |
| node objects. So if you have a list of media samples | |
| and you know there will never be more than five active at | |
| any given time of them for example then override the default | |
| constructor | |
| */ | |
| m_Cache.AddToCache(pCurrent); | |
| /* If the list is empty then reset the list event */ | |
| --m_Count; | |
| ASSERT(m_Count >= 0); | |
| return pObject; | |
| } // Remove | |
| /* Add this object to the tail end of our list | |
| Return the new tail position. | |
| */ | |
| __out_opt POSITION CBaseList::AddTailI(__in void *pObject) | |
| { | |
| /* Lock the critical section before continuing */ | |
| CNode *pNode; | |
| // ASSERT(pObject); // NULL pointers in the list are allowed. | |
| /* If there is a node objects in the cache then use | |
| that otherwise we will have to create a new one */ | |
| pNode = (CNode *) m_Cache.RemoveFromCache(); | |
| if (pNode == NULL) { | |
| pNode = new CNode; | |
| } | |
| /* Check we have a valid object */ | |
| if (pNode == NULL) { | |
| return NULL; | |
| } | |
| /* Initialise all the CNode object | |
| just in case it came from the cache | |
| */ | |
| pNode->SetData(pObject); | |
| pNode->SetNext(NULL); | |
| pNode->SetPrev(m_pLast); | |
| if (m_pLast == NULL) { | |
| m_pFirst = pNode; | |
| } else { | |
| m_pLast->SetNext(pNode); | |
| } | |
| /* Set the new last node pointer and also increment the number | |
| of list entries, the critical section is unlocked when we | |
| exit the function | |
| */ | |
| m_pLast = pNode; | |
| ++m_Count; | |
| return (POSITION) pNode; | |
| } // AddTail(object) | |
| /* Add this object to the head end of our list | |
| Return the new head position. | |
| */ | |
| __out_opt POSITION CBaseList::AddHeadI(__in void *pObject) | |
| { | |
| CNode *pNode; | |
| // ASSERT(pObject); // NULL pointers in the list are allowed. | |
| /* If there is a node objects in the cache then use | |
| that otherwise we will have to create a new one */ | |
| pNode = (CNode *) m_Cache.RemoveFromCache(); | |
| if (pNode == NULL) { | |
| pNode = new CNode; | |
| } | |
| /* Check we have a valid object */ | |
| if (pNode == NULL) { | |
| return NULL; | |
| } | |
| /* Initialise all the CNode object | |
| just in case it came from the cache | |
| */ | |
| pNode->SetData(pObject); | |
| /* chain it in (set four pointers) */ | |
| pNode->SetPrev(NULL); | |
| pNode->SetNext(m_pFirst); | |
| if (m_pFirst == NULL) { | |
| m_pLast = pNode; | |
| } else { | |
| m_pFirst->SetPrev(pNode); | |
| } | |
| m_pFirst = pNode; | |
| ++m_Count; | |
| return (POSITION) pNode; | |
| } // AddHead(object) | |
| /* Add all the elements in *pList to the tail of this list. | |
| Return TRUE if it all worked, FALSE if it didn't. | |
| If it fails some elements may have been added. | |
| */ | |
| BOOL CBaseList::AddTail(__in CBaseList *pList) | |
| { | |
| /* lock the object before starting then enumerate | |
| each entry in the source list and add them one by one to | |
| our list (while still holding the object lock) | |
| Lock the other list too. | |
| */ | |
| POSITION pos = pList->GetHeadPositionI(); | |
| while (pos) { | |
| if (NULL == AddTailI(pList->GetNextI(pos))) { | |
| return FALSE; | |
| } | |
| } | |
| return TRUE; | |
| } // AddTail(list) | |
| /* Add all the elements in *pList to the head of this list. | |
| Return TRUE if it all worked, FALSE if it didn't. | |
| If it fails some elements may have been added. | |
| */ | |
| BOOL CBaseList::AddHead(__in CBaseList *pList) | |
| { | |
| /* lock the object before starting then enumerate | |
| each entry in the source list and add them one by one to | |
| our list (while still holding the object lock) | |
| Lock the other list too. | |
| To avoid reversing the list, traverse it backwards. | |
| */ | |
| POSITION pos; | |
| INTERNALREVERSETRAVERSELIST(*pList, pos) { | |
| if (NULL== AddHeadI(pList->GetValidI(pos))){ | |
| return FALSE; | |
| } | |
| } | |
| return TRUE; | |
| } // AddHead(list) | |
| /* Add the object after position p | |
| p is still valid after the operation. | |
| AddAfter(NULL,x) adds x to the start - same as AddHead | |
| Return the position of the new object, NULL if it failed | |
| */ | |
| __out_opt POSITION CBaseList::AddAfterI(__in_opt POSITION pos, __in void * pObj) | |
| { | |
| if (pos==NULL) | |
| return AddHeadI(pObj); | |
| /* As someone else might be furkling with the list - | |
| Lock the critical section before continuing | |
| */ | |
| CNode *pAfter = (CNode *) pos; | |
| ASSERT(pAfter != NULL); | |
| if (pAfter==m_pLast) | |
| return AddTailI(pObj); | |
| /* set pnode to point to a new node, preferably from the cache */ | |
| CNode *pNode = (CNode *) m_Cache.RemoveFromCache(); | |
| if (pNode == NULL) { | |
| pNode = new CNode; | |
| } | |
| /* Check we have a valid object */ | |
| if (pNode == NULL) { | |
| return NULL; | |
| } | |
| /* Initialise all the CNode object | |
| just in case it came from the cache | |
| */ | |
| pNode->SetData(pObj); | |
| /* It is to be added to the middle of the list - there is a before | |
| and after node. Chain it after pAfter, before pBefore. | |
| */ | |
| CNode * pBefore = pAfter->Next(); | |
| ASSERT(pBefore != NULL); | |
| /* chain it in (set four pointers) */ | |
| pNode->SetPrev(pAfter); | |
| pNode->SetNext(pBefore); | |
| pBefore->SetPrev(pNode); | |
| pAfter->SetNext(pNode); | |
| ++m_Count; | |
| return (POSITION) pNode; | |
| } // AddAfter(object) | |
| BOOL CBaseList::AddAfter(__in_opt POSITION p, __in CBaseList *pList) | |
| { | |
| POSITION pos; | |
| INTERNALTRAVERSELIST(*pList, pos) { | |
| /* p follows along the elements being added */ | |
| p = AddAfterI(p, pList->GetValidI(pos)); | |
| if (p==NULL) return FALSE; | |
| } | |
| return TRUE; | |
| } // AddAfter(list) | |
| /* Mirror images: | |
| Add the element or list after position p. | |
| p is still valid after the operation. | |
| AddBefore(NULL,x) adds x to the end - same as AddTail | |
| */ | |
| __out_opt POSITION CBaseList::AddBeforeI(__in_opt POSITION pos, __in void * pObj) | |
| { | |
| if (pos==NULL) | |
| return AddTailI(pObj); | |
| /* set pnode to point to a new node, preferably from the cache */ | |
| CNode *pBefore = (CNode *) pos; | |
| ASSERT(pBefore != NULL); | |
| if (pBefore==m_pFirst) | |
| return AddHeadI(pObj); | |
| CNode * pNode = (CNode *) m_Cache.RemoveFromCache(); | |
| if (pNode == NULL) { | |
| pNode = new CNode; | |
| } | |
| /* Check we have a valid object */ | |
| if (pNode == NULL) { | |
| return NULL; | |
| } | |
| /* Initialise all the CNode object | |
| just in case it came from the cache | |
| */ | |
| pNode->SetData(pObj); | |
| /* It is to be added to the middle of the list - there is a before | |
| and after node. Chain it after pAfter, before pBefore. | |
| */ | |
| CNode * pAfter = pBefore->Prev(); | |
| ASSERT(pAfter != NULL); | |
| /* chain it in (set four pointers) */ | |
| pNode->SetPrev(pAfter); | |
| pNode->SetNext(pBefore); | |
| pBefore->SetPrev(pNode); | |
| pAfter->SetNext(pNode); | |
| ++m_Count; | |
| return (POSITION) pNode; | |
| } // Addbefore(object) | |
| BOOL CBaseList::AddBefore(__in_opt POSITION p, __in CBaseList *pList) | |
| { | |
| POSITION pos; | |
| INTERNALREVERSETRAVERSELIST(*pList, pos) { | |
| /* p follows along the elements being added */ | |
| p = AddBeforeI(p, pList->GetValidI(pos)); | |
| if (p==NULL) return FALSE; | |
| } | |
| return TRUE; | |
| } // AddBefore(list) | |
| /* Split *this after position p in *this | |
| Retain as *this the tail portion of the original *this | |
| Add the head portion to the tail end of *pList | |
| Return TRUE if it all worked, FALSE if it didn't. | |
| e.g. | |
| foo->MoveToTail(foo->GetHeadPosition(), bar); | |
| moves one element from the head of foo to the tail of bar | |
| foo->MoveToTail(NULL, bar); | |
| is a no-op | |
| foo->MoveToTail(foo->GetTailPosition, bar); | |
| concatenates foo onto the end of bar and empties foo. | |
| A better, except excessively long name might be | |
| MoveElementsFromHeadThroughPositionToOtherTail | |
| */ | |
| BOOL CBaseList::MoveToTail | |
| (__in_opt POSITION pos, __in CBaseList *pList) | |
| { | |
| /* Algorithm: | |
| Note that the elements (including their order) in the concatenation | |
| of *pList to the head of *this is invariant. | |
| 1. Count elements to be moved | |
| 2. Join *pList onto the head of this to make one long chain | |
| 3. Set first/Last pointers in *this and *pList | |
| 4. Break the chain at the new place | |
| 5. Adjust counts | |
| 6. Set/Reset any events | |
| */ | |
| if (pos==NULL) return TRUE; // no-op. Eliminates special cases later. | |
| /* Make cMove the number of nodes to move */ | |
| CNode * p = (CNode *)pos; | |
| int cMove = 0; // number of nodes to move | |
| while(p!=NULL) { | |
| p = p->Prev(); | |
| ++cMove; | |
| } | |
| /* Join the two chains together */ | |
| if (pList->m_pLast!=NULL) | |
| pList->m_pLast->SetNext(m_pFirst); | |
| if (m_pFirst!=NULL) | |
| m_pFirst->SetPrev(pList->m_pLast); | |
| /* set first and last pointers */ | |
| p = (CNode *)pos; | |
| if (pList->m_pFirst==NULL) | |
| pList->m_pFirst = m_pFirst; | |
| m_pFirst = p->Next(); | |
| if (m_pFirst==NULL) | |
| m_pLast = NULL; | |
| pList->m_pLast = p; | |
| /* Break the chain after p to create the new pieces */ | |
| if (m_pFirst!=NULL) | |
| m_pFirst->SetPrev(NULL); | |
| p->SetNext(NULL); | |
| /* Adjust the counts */ | |
| m_Count -= cMove; | |
| pList->m_Count += cMove; | |
| return TRUE; | |
| } // MoveToTail | |
| /* Mirror image of MoveToTail: | |
| Split *this before position p in *this. | |
| Retain in *this the head portion of the original *this | |
| Add the tail portion to the start (i.e. head) of *pList | |
| Return TRUE if it all worked, FALSE if it didn't. | |
| e.g. | |
| foo->MoveToHead(foo->GetTailPosition(), bar); | |
| moves one element from the tail of foo to the head of bar | |
| foo->MoveToHead(NULL, bar); | |
| is a no-op | |
| foo->MoveToHead(foo->GetHeadPosition, bar); | |
| concatenates foo onto the start of bar and empties foo. | |
| */ | |
| BOOL CBaseList::MoveToHead | |
| (__in_opt POSITION pos, __in CBaseList *pList) | |
| { | |
| /* See the comments on the algorithm in MoveToTail */ | |
| if (pos==NULL) return TRUE; // no-op. Eliminates special cases later. | |
| /* Make cMove the number of nodes to move */ | |
| CNode * p = (CNode *)pos; | |
| int cMove = 0; // number of nodes to move | |
| while(p!=NULL) { | |
| p = p->Next(); | |
| ++cMove; | |
| } | |
| /* Join the two chains together */ | |
| if (pList->m_pFirst!=NULL) | |
| pList->m_pFirst->SetPrev(m_pLast); | |
| if (m_pLast!=NULL) | |
| m_pLast->SetNext(pList->m_pFirst); | |
| /* set first and last pointers */ | |
| p = (CNode *)pos; | |
| if (pList->m_pLast==NULL) | |
| pList->m_pLast = m_pLast; | |
| m_pLast = p->Prev(); | |
| if (m_pLast==NULL) | |
| m_pFirst = NULL; | |
| pList->m_pFirst = p; | |
| /* Break the chain after p to create the new pieces */ | |
| if (m_pLast!=NULL) | |
| m_pLast->SetNext(NULL); | |
| p->SetPrev(NULL); | |
| /* Adjust the counts */ | |
| m_Count -= cMove; | |
| pList->m_Count += cMove; | |
| return TRUE; | |
| } // MoveToHead | |
| /* Reverse the order of the [pointers to] objects in *this | |
| */ | |
| void CBaseList::Reverse() | |
| { | |
| /* algorithm: | |
| The obvious booby trap is that you flip pointers around and lose | |
| addressability to the node that you are going to process next. | |
| The easy way to avoid this is do do one chain at a time. | |
| Run along the forward chain, | |
| For each node, set the reverse pointer to the one ahead of us. | |
| The reverse chain is now a copy of the old forward chain, including | |
| the NULL termination. | |
| Run along the reverse chain (i.e. old forward chain again) | |
| For each node set the forward pointer of the node ahead to point back | |
| to the one we're standing on. | |
| The first node needs special treatment, | |
| it's new forward pointer is NULL. | |
| Finally set the First/Last pointers | |
| */ | |
| CNode * p; | |
| // Yes we COULD use a traverse, but it would look funny! | |
| p = m_pFirst; | |
| while (p!=NULL) { | |
| CNode * q; | |
| q = p->Next(); | |
| p->SetNext(p->Prev()); | |
| p->SetPrev(q); | |
| p = q; | |
| } | |
| p = m_pFirst; | |
| m_pFirst = m_pLast; | |
| m_pLast = p; | |
| #if 0 // old version | |
| if (m_pFirst==NULL) return; // empty list | |
| if (m_pFirst->Next()==NULL) return; // single node list | |
| /* run along forward chain */ | |
| for ( p = m_pFirst | |
| ; p!=NULL | |
| ; p = p->Next() | |
| ){ | |
| p->SetPrev(p->Next()); | |
| } | |
| /* special case first element */ | |
| m_pFirst->SetNext(NULL); // fix the old first element | |
| /* run along new reverse chain i.e. old forward chain again */ | |
| for ( p = m_pFirst // start at the old first element | |
| ; p->Prev()!=NULL // while there's a node still to be set | |
| ; p = p->Prev() // work in the same direction as before | |
| ){ | |
| p->Prev()->SetNext(p); | |
| } | |
| /* fix forward and reverse pointers | |
| - the triple XOR swap would work but all the casts look hideous */ | |
| p = m_pFirst; | |
| m_pFirst = m_pLast; | |
| m_pLast = p; | |
| #endif | |
| } // Reverse |