How to: Make a Type-Safe Collection
This article explains how to make type-safe collections for your own data types. Topics include:
The Microsoft Foundation Class Library provides predefined type-safe collections based on C++ templates. Because they are templates, these classes help provide type safety and ease of use without the type-casting and other extra work involved in using a nontemplate class for this purpose. The MFC sample COLLECT demonstrates the use of template-based collection classes in an MFC application. In general, use these classes any time you write new collections code.
Using Template-Based Classes for Type Safety
To use template-based classes
Declare a variable of the collection class type. For example:
CList<int, int> m_intList;
Call the member functions of the collection object. For example:
m_intList.AddTail(100); m_intList.RemoveAll();
If necessary, implement the helper functions and SerializeElements. For information on implementing these functions, see Implementing Helper Functions.
This example shows the declaration of a list of integers. The first parameter in step 1 is the type of data stored as elements of the list. The second parameter specifies how the data is to be passed to and returned from member functions of the collection class, such as Add
and GetAt
.
Implementing Helper Functions
The template-based collection classes CArray
, CList
, and CMap
use five global helper functions that you can customize as needed for your derived collection class. For information on these helper functions, see Collection Class Helpers in the MFC Reference. Implementation of the serialization function is necessary for most uses of the template-based collection classes.
Serializing Elements
The CArray
, CList
, and CMap
classes call SerializeElements
to store collection elements to or read them from an archive.
The default implementation of the SerializeElements
helper function does a bitwise write from the objects to the archive, or a bitwise read from the archive to the objects, depending on whether the objects are being stored in or retrieved from the archive. Override SerializeElements
if this action is not appropriate.
If your collection stores objects derived from CObject
and you use the IMPLEMENT_SERIAL
macro in the implementation of the collection element class, you can take advantage of the serialization functionality built into CArchive
and CObject
:
CArray< CPerson, CPerson& > personArray;
template <> void AFXAPI SerializeElements <CPerson>(CArchive& ar,
CPerson* pNewPersons, INT_PTR nCount)
{
for (int i = 0; i < nCount; i++, pNewPersons++)
{
// Serialize each CPerson object
pNewPersons->Serialize(ar);
}
}
The overloaded insertion operators for CArchive
call CObject::Serialize
(or an override of that function) for each CPerson
object.
Using Nontemplate Collection Classes
MFC also supports the collection classes introduced with MFC version 1.0. These classes are not based on templates. They can be used to contain data of the supported types CObject*
, UINT
, DWORD
, and CString
. You can use these predefined collections (such as CObList
) to hold collections of any objects derived from CObject
. MFC also provides other predefined collections to hold primitive types such as UINT
and void pointers (void*
). In general, however, it is often useful to define your own type-safe collections to hold objects of a more specific class and its derivatives. Note that doing so with the collection classes not based on templates is more work than using the template-based classes.
There are two ways to create type-safe collections with the nontemplate collections:
Use the nontemplate collections, with type casting if necessary. This is the easier approach.
Derive from and extend a nontemplate type-safe collection.
To use the nontemplate collections with type casting
Use one of the nontemplate classes, such as
CWordArray
, directly.For example, you can create a
CWordArray
and add any 32-bit values to it, then retrieve them. There is nothing more to do. You just use the predefined functionality.You can also use a predefined collection, such as
CObList
, to hold any objects derived fromCObject
. ACObList
collection is defined to hold pointers toCObject
. When you retrieve an object from the list, you may have to cast the result to the proper type since theCObList
functions return pointers toCObject
. For example, if you storeCPerson
objects in aCObList
collection, you have to cast a retrieved element to be a pointer to aCPerson
object. The following example uses aCObList
collection to holdCPerson
objects:CPerson* p1 = new CPerson(); CObList myList; myList.AddHead(p1); // No cast needed CPerson* p2 = (CPerson*)myList.GetHead();
This technique of using a predefined collection type and casting as necessary may be adequate for many of your collection needs. If you need further functionality or more type safety, use a template-based class, or follow the next procedure.
To derive and extend a nontemplate type-safe collection
Derive your own collection class from one of the predefined nontemplate classes.
When you derive your class, you can add type-safe wrapper functions to provide a type-safe interface to existing functions.
For example, if you derived a list from
CObList
to holdCPerson
objects, you might add the wrapper functionsAddHeadPerson
andGetHeadPerson
, as shown below.class CPersonList : public CObList { public: void AddHeadPerson(CPerson* person) { AddHead(person); } const CPerson* GetHeadPerson() { return (CPerson*)GetHead(); } };
These wrapper functions provide a type-safe way to add and retrieve
CPerson
objects from the derived list. You can see that for theGetHeadPerson
function, you are simply encapsulating the type casting.You can also add new functionality by defining new functions that extend the capabilities of the collection rather than just wrapping existing functionality in type-safe wrappers. For example, the article Deleting All Objects in a CObject Collection describes a function to delete all the objects contained in a list. This function could be added to the derived class as a member function.