The CObList Collection Class

Once you get to know the collection classes, you'll wonder how you ever got along without them. The CObList class is a useful representative of the collection class family. If you're familiar with this class, it's easy to learn the other list classes, the array classes, and the map classes.

You might think that collections are something new, but the C programming language has always supported one kind of collection—the array. C arrays must be fixed in size, and they do not support insertion of elements. Many C programmers have written function libraries for other collections, including linked lists, dynamic arrays, and indexed dictionaries. For implementing collections, the C++ class is an obvious and better alternative than a C function library. A list object, for example, neatly encapsulates the list's internal data structures.

The CObList class supports ordered lists of pointers to objects of classes derived from CObject. Another MFC collection class, CPtrList, stores void pointers instead of CObject pointers. Why not use CPtrList instead? The CObList class offers advantages for diagnostic dumping, which you'll see in this chapter, and for serialization, which you'll see in the 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:

  1. It returns a pointer to the "current" object in the list, identified by the incoming value of the POSITION parameter.

  2. 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 is going to 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 are a relatively new C++ language element, introduced by Microsoft 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 are 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 statement

#ifdef _DEBUG
    afxDump << actionList;
#endif

produces 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 is called for the object's class and the appropriate class name is printed.