The CObList Collection Class
next chapter. One important feature of CObList is that it can contain mixed pointers. In other words, a CObList collection can hold pointers to both CStudent objects and CTeacher objects, assuming that both CStudent and CTeacher were derived from CObject.
Using the CObList Class for a First-In, First-Out List
One of the easiest ways to use a CObList object is to add new elements to the tail, or bottom, of the list and to remove elements from the head, or top, of the list. The first element added to the list will always be the first element removed from the head of the list. Suppose you're working with element objects of class CAction, which is your own custom class derived from CObject. A command-line program that puts five elements into a list and then retrieves them in the same sequence is shown here:
#include <afx.h>
#include <afxcoll.h>
class CAction : public CObject
{
private:
int m_nTime;
public:
CAction(int nTime) { m_nTime = nTime; } // Constructor stores
// integer time value
void PrintTime() { TRACE("time = %d\n", m_nTime); }
};
int main()
{
CAction* pAction;
CObList actionList; // action list constructed on stack
int i;
// inserts action objects in sequence {0, 1, 2, 3, 4}
for (i = 0; i < 5; i++) {
pAction = new CAction(i);
actionList.AddTail(pAction); // no cast necessary for pAction
}
// retrieves and removes action objects in sequence {0, 1, 2, 3, 4}
while (!actionList.IsEmpty()) {
pAction = // cast required for
(CAction*) actionList.RemoveHead(); // return value
pAction->PrintTime();
delete pAction;
}
return 0;
}
Here's what's going on in the program. First, a CObList object, actionList, is constructed. Then the CObList::AddTail member function inserts pointers to newly constructed CAction objects. No casting is necessary for pAction because AddTail takes a CObject pointer parameter and pAction is a pointer to a derived class.Next, the CAction object pointers are removed from the list of the objects deleted. A cast is necessary for the returned value of RemoveHead because RemoveHead returns a CObject pointer that is higher in the class hierarchy than CAction.When you remove an object pointer from a collection, the object is not automatically deleted. The delete statement is necessary for deleting the CAction objects.
CObList Iteration: The POSITION Variable
Suppose you want to iterate through the elements in a list. The CObList class provides a GetNext member function that returns a pointer to the "next" list element, but using it is a little tricky. GetNext takes a parameter of type POSITION, which is a 32-bit variable. The POSITION variable is an internal representation of the retrieved element's position in the list. Because the POSITION parameter is declared as a reference (&), the function can change its value.
GetNext does the following:
It returns a pointer to the "current" object in the list, which is identified by the incoming value of the POSITION parameter.
It increments the value of the POSITION parameter to the next list element.
Here's what a GetNext loop looks like, assuming you're using the list generated in the previous example:
CAction* pAction;
POSITION pos = actionList.GetHeadPosition();
while (pos != NULL) {
pAction = (CAction*) actionList.GetNext(pos);
pAction->PrintTime();
}
Now suppose you have an interactive Windows-based application that uses toolbar buttons to sequence forward and backward through the list one element at a time. You can't use GetNext to retrieve the entry because GetNext always increments the POSITION variable and you don't know in advance whether the user will want the next element or the previous element. Here's a sample view class command message handler function that gets the next list entry. In the CMyView class, m_actionList is an embedded CObList object and the m_position data member is a POSITION variable that holds the current list position.
CMyView::OnCommandNext()
{
POSITION pos;
CAction* pAction;
if ((pos = m_position) != NULL) {
m_actionList.GetNext(pos);
if (pos != NULL) { // pos is NULL at end of list
pAction = (CAction*) m_actionList.GetAt(pos);
pAction->PrintTime();
m_position = pos;
}
else {
AfxMessageBox("End of list reached");
}
}
}
GetNext is now called first to increment the list position, and the CObList::GetAt member function is called to retrieve the entry. The m_position variable is updated only when we're sure we're not at the tail of the list.
The CTypedPtrList Template Collection Class
The CObList class works fine if you want a collection to contain mixed pointers. If, on the other hand, you want a type-safe collection that contains only one type of object pointer, you should look at the MFC library template pointer collection classes. CTypedPtrList is a good example. Templates were introduced in Visual C++ version 2.0. CTypedPtrList is a template class that you can use to create a list of any pointers to objects of any specified class. To make a long story short, you use the template to create a custom derived list class, using either CPtrList or CObList as a base class.To declare an object for CAction pointers, you write the following line of code:
CTypedPtrList<CObList, CAction*> m_actionList;
The first parameter is the base class for the collection, and the second parameter is the type for parameters and return values. Only CPtrList and CObList are permitted for the base class because those are the only two MFC library pointer list classes. If you're storing objects of classes derived from CObject, you should use CObList as your base class; otherwise, use CPtrList.By using the template as shown above, the compiler ensures that all list member functions return a CAction pointer. Thus, you can write the following code:
pAction = m_actionList.GetAt(pos); // no cast required
If you want to clean up the notation a little, use a typedef statement to generate what looks like a class, as shown here:
typedef CTypedPtrList<CObList, CAction*> CActionList;
Now you can declare m_actionList as follows:
CActionList m_actionList;
The Dump Context and Collection Classes
The Dump function for CObList and the other collection classes has a useful property. If you call Dump for a collection object, you can get a display of each object in the collection. If the element objects use the DECLARE_DYNAMIC and IMPLEMENT_DYNAMIC macros, the dump will show the class name for each object.
The default behavior of the collection Dump functions is to display only class names and addresses of element objects. If you want the collection Dump functions to call the Dump function for each element object, you must, somewhere at the start of your program, make the following call:
#ifdef _DEBUG
afxDump.SetDepth(1);
#endif
Now the following statement
#ifdef _DEBUG
afxDump << actionList;
#endif
will produce output such as this:
a CObList at $411832
with 4 elements
a CAction at $412CD6
time = 0
a CAction at $412632
time = 1
a CAction at $41268E
time = 2
a CAction at $4126EA
time = 3
If the collection contains mixed pointers, the virtual Dump function will be called for the object's class and the appropriate class name will be printed.