This discussion does not instruct you to add any new code to your files. Code is sometimes repeated to illustrate a point, but you do not need to add it.
A CPersonList object is a “collection” of CPerson objects. You construct a CPersonList object from the CPersonList class declared above. CPersonList is derived publicly from the Microsoft Foundation Class CObList.
Figure 2.4 shows a collection schematically.
The discussion that follows explains how to construct a CPersonList object, how to add person objects to it, how to serialize the whole list, and how to search the list for all persons with a given last name.
Collection Classes
The Microsoft Foundation Class Library supplies fourteen collection classes in three categories:
Classes in the first category encapsulate arrays. The Microsoft Foundation Class Library provides arrays of bytes, words, double words, pointers to objects, objects, and strings.
Classes in the second category encapsulate linked lists. The Microsoft Foundation Class Library provides lists of pointers, objects, and strings. For example, the class CObList can contain CObjects or objects of any class derived from CObject, such as CPerson. CPersonList is derived from CObList.
Classes in the third category encapsulate maps. A map is a collection that maps one kind of data to another. For example, the Microsoft Foundation Class Library includes class CMapStringToOb. This class contains mappings from strings to CObjects. This class lets you store objects and look them up by string-type key values. It is a good alternative for implementing CPersonList if you want to experiment.
For more information about the Microsoft Foundation Class Library's collection classes, see the Class Libraries Reference and Chapter 9.
How to Construct a CPersonList Object
The constructor for this class is declared publicly because you must be able to invoke it from outside the class. A list requires no initialization values, so you don't need a second constructor that takes arguments. The constructor of CPersonList simply initializes the list's m_bIsDirty member variable to FALSE, signifying that the list currently has no unsaved changes.
·To construct a CPersonList:
Use one of the two ways you construct a CPerson object:
On the frame of a function
When a function executes, a list declared as a local variable is constructed. When the function returns, the list's destructor is called to destroy the list. For example, to construct a local list in a function:
void AFunction()
{
CPersonList myList; // List is constructed
// Operations on the list
// List is destroyed as function exits
}
In the heap
A list constructed dynamically with the C++ new operator exists until
you explicitly destroy it by invoking the C++ delete operator. To create a new CPersonList in the heap:
CPersonList* pMyList = new CPersonList;
This invokes the CPersonList constructor for pMyList.
How to Add Persons to the List
This section shows how to create and add person objects to a list object. You do not need to add this code to your files.
·To add a person object to the list:
1.Create a person object:
CPerson* pNewPerson = new CPerson( "Smith",
"Mary",
"435-8159" );
2.Add the person to the list:
pMyList -> AddHead( pNewPerson );
This code calls the AddHead member function of CPersonList, which the class inherits from CObList without overriding. Because the list was created as a pointer to a CPersonList object, the –> operator is used to access the member function.
This section explains how to serialize the CPerson database; that is, to write the data members of all CPerson objects in the list to disk. It also explains the reverse process: deserializing (reading) the database back from disk. In both cases, serialization uses a CFile object and a CArchive object. These objects are discussed in the next section below.
The process also optionally uses a CFileException object. The CFileException object is used to return information about any file errors that may have occurred during the attempt to open the file. For more information about serialization, see “More on Serialization” on page 60. For more information about exceptions, see “Exception Handling” on page 61.
·To serialize a CPersonList:
1.Create a CFile object and call its Open member function.
In the call to Open, specify the open permissions. Because the permissions are defined inside class CFile, you need to qualify their identifiers with the CFile class name:
theFile.Open( pszFileName, CFile::modeCreate | CFile::modeWrite )
The arguments to Open specify the filename, the access mode
( CFile::modeRead or CFile::modeWrite), and, optionally, a preconstructed CFileException object (not used here).
Note:
The filename argument is a C++ reference to a CString object. You can pass an ordinary null-terminated C-language string in a CString argument, as is done with the szFileName string in DMTEST.CPP. For more information about CString, see the Class Libraries Reference and Chapter 7.
2.Use the CFile object to create a CArchive object:
CArchive theOutArchive( &theOutFile, CArchive::store );
This example shows an archive created for writing. The second argument specifies whether the archive is for loading (reading) or storing (writing). For more details, see “More on Serialization” on page 60.
3.Use the CArchive object as you would a C++ iostream, such as cin or cout:
theOutArchive << pList;
You use the overloaded insertion (<<) or extraction (>>) operator to pass data through the archive object. The one you use depends on whether you are writing or reading the list.
4.Close the archive object and then the file object, in that order:
theOutArchive.Close();
theOutFile.Close();
If you close them out of order, an exception is thrown.
The default serialization behavior of a collection is to serialize all its elements.
Because all the elements of a CPersonList are CPerson objects, and
because CPerson objects know how to serialize themselves, you can rely on the default behavior for a correctly serialized list. This means that you can serialize a CPersonList and all its elements to or from a CArchive with a single statement. The code fragments below show how easily this can be done (don't add this code to your files):
CPersonList* pList = new CPersonList;
// Add CPerson elements
.
.
.
// To serialize the collection out to disk
// Create a file object
CFile theOutFile;
// Open the file
if( !theOutFile.Open( pszFileName, CFile::modeCreate || CFile::modeWrite ), NULL )
{
// Error handling
TRACE( "Unable to open a file for serialization\n" );
return FALSE;
}
// Create an archive object from the file object
CArchive theOutArchive( &theOutFile, CArchive::store );
// Serialize
theOutArchive << pList;
// Close the archive and the file, in that order
theOutArchive.Close();
theOutFile.Close();
And to deserialize the collection back in from disk:
CPersonList* pOtherList = NULL;
CFile theInFile;
if( !theInFile.Open( pszFileName, CFile::modeRead ) )
{
// Error handling
}
// Create an archive object for reading
CArchive theInArchive( &theInFile, CArchive::load );
// Deserialize
theInArchive >> pOtherList;
// Close the archive and the file, in that order
As the code shows, a single insertion or extraction statement is sufficient to completely serialize the entire collection. Type-safety is maintained during the serialization operation. Thus, the serialization mechanism checks the type of each object in the file before it is added to the list and throws an exception if it encounters an incorrect object type. Figure 2.5 shows the steps in serializing a list of objects.
Note:
You must use the DECLARE_SERIAL macro in the declaration of a class for the class to be serializable. Look at the declarations of CPerson and CPersonList discussed previously. You must also use the IMPLEMENT_SERIAL macro in the .CPP file that defines the member functions declared in your .H file.
The Microsoft Foundation Class Library provides class CFile and class CArchive for working with disk files. You can still use standard C I/O routines, but these classes are a good alternative because they encapsulate file handling in objects. For more information about CFile, CArchive, and serialization, see “More on Serialization” on page 60.
This section explains how to search the CPersonList for a particular person. In the example program, the last name is used as the key for the search. The steps are described below:
·To search a CPersonList:
1.Call the target list's FindPerson member function:
CPersonList* pFound = pDataBase -> FindPerson( szLastName );
FindPerson takes an argument of type CString. You can pass an initialized CString object or a null-terminated string containing the last name to find. For more information on the properties of a CString object, see Chapter 7.
FindPerson returns a new CPersonList object, pFound. If there were any finds, pFound contains pointers to the objects found in the target list, pDataBase.
Note:
The pointers in the new list point to the original objects, which are still in the original target list.
2.Examine the returned list to see if it contains objects:
if( !pFound -> IsEmpty() )
{
// Do something with the found list
}
CPersonList inherits an IsEmpty member function from CObList. It returns TRUE if the list is empty.
3.Delete the found list when you finish with it:
delete pFound;
The found list is allocated dynamically by FindPerson, which returns a pointer to the found list. Because pFound is allocated in the heap, you must deallocate its storage with the delete operator.
Remember that the found list's elements are pointers to CPerson objects still in the original target list. Don't call the DeleteAll member function of pFound. That would destroy the objects in the target list. All you want to do is delete the found list, which leaves the target list intact.
In the code for FindPerson, you can see a number of objects and their member functions in use. To iterate over the list in search of the key last name, FindPerson uses the GetHeadPosition and GetNext member functions that CPersonList inherits from CObList.
GetHeadPosition is used to start at the beginning of the list, and GetNext is used to get access to successive elements of the list. To compare the key string with the last name member variable of each CPerson object in the list, FindPerson calls the _strnicmp run-time function and passes it the last name of the next person in the list.
CString has several member functions for string comparison—these are comparable to the C run-time library functions for string comparison. To obtain the last name of the next person in the list, FindPerson calls the person object's GetLastName member function, which you wrote as part of class CPerson.
If matching last names are found, FindPerson returns a list containing pointers to all found objects. The returned list can be useful in its own right, since it is a CPersonList object with all the capabilities of the original list from which it was built. You can display the found list, operate on it, add to it, delete from it, and so on.
How to Delete the Entire Database
This section explains how to delete the database and its contents after you finish using it.
·To delete the entire database:
1.Call the CPersonList object's DeleteAll member function to delete the contents:
pDataBase -> DeleteAll();
The DeleteAll member function, which you added to CPersonList, performs two operations. First, it iterates through the list and invokes the delete operator for each contained object in turn. Then it calls the CObList member function RemoveAll, which frees underlying storage and marks the list as empty. After the RemoveAll operation, the list contains no pointers to any objects.
2.Invoke the C++ delete operator to delete the list object:
delete pDataBase;
The reason you added a DeleteAll member function to CPersonList is that deleting objects from the list and removing them from the list are not the same thing. If you delete an object from the list, the list still has a pointer to the object, but this pointer is now invalid because the object it formerly pointed to no longer exists.
On the other hand, if you remove an object from the list, you remove the list's pointer to the object, but the object itself still exists. Thus, if you want to keep a list but empty it without destroying the objects it contained, use RemoveAll. If you want to destroy the contents of a list without destroying the list itself, use DeleteAll. If you want to destroy both the list and its contents, first call DeleteAll, then invoke delete on the list object. Figure 2.6 summarizes these deletion processes.
The DeleteAll member function of class CPersonList uses two other useful member functions inherited from class CObList. The GetHeadPosition function returns a value of type POSITION, which provides access to the head element of the list. The GetNext function returns the POSITION of the next element. You can access the element with this POSITION value. To see these member functions in use, see the code for DeleteAll in file PERSON.CPP in Listing 2. For more information about class CObList, see the Class Libraries Reference.