Collections: How to Make a Type-Safe Collection

HomeOverviewHow Do ISampleTutorial

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 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 Advanced Concepts 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

  1. Declare a variable of the collection class type. For example:
    CList<int, int> m_intList;
    
  2. Call the member functions of the collection object. For example:
    m_intList.AddTail( 100 );
    m_intList.RemoveAll( );
    
  3. If necessary, implement the helper functions SerializeElements, ConstructElements, and DestructElements. 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 seven 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 Class Library Reference. Three of these helper functions are used in constructing, destructing, and serializing collection elements; implementations of these functions are necessary for most uses of the template-based collection classes.

Construction and Destruction

The helper functions ConstructElements and DestructElements are called by members that respectively add and remove elements from a collection.

Helper Called directly by Called indirectly by
ConstructElements CArray::SetSize
CArray::InsertAt
CList::AddHead
CList::AddTail
CList::InsertBefore
CList::InsertAfter
CMap::operator [ ]
DestructElements CArray::SetSize
CArray::RemoveAt
CList::RemoveAll
CMap::RemoveAll
CList::RemoveHead
CList::RemoveTail
CList::RemoveAt
CMap::RemoveKey

You should override these functions if their default action is not suitable for your collection class. The default implementation of ConstructElements sets to 0 the memory that is allocated for new elements of the collection and calls the constructor of each element. The default implementation of DestructElements calls the destructor for each element.

In general, overriding ConstructElements is necessary whenever the collection stores objects that require a call to a constructor (or other initializing function), or when the objects have members requiring such calls. Overriding DestructElements is necessary when an object requires special action, such as freeing memory allocated from the heap, when the object is destroyed.

For example, you might override ConstructElements for an array of CPerson objects as follows:

class CPerson : public CObject { . . . };
CArray< CPerson, CPerson& > personArray;

template <> void AFXAPI ConstructElements <CPerson> ( CPerson* pNewPersons, int nCount )
{
    for ( int i = 0; i < nCount; i++, pNewPersons++ )
    {
        // call CPerson default constructor directly
        new( pNewPersons )CPerson;
    }
}

This override iterates through the new CPerson objects, calling each object’s constructor. The special new operator used here constructs a new CPerson object in place rather than allocating memory from the heap.

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:

class CPerson : public CObject { . . . };
CArray< CPerson, CPerson& > personArray;

template <> void AFXAPI SerializeElements <CPerson> ( CArchive& ar, CPerson* pNewPersons, int 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:

  1. Use the nontemplate collections, with type casting if necessary. This is the easier approach.

  2. Derive from and extend a nontemplate type-safe collection.

To use the nontemplate collections with type casting

To derive and extend a nontemplate type-safe collection

For related information, see Technical Note 4: C++ Template Tool.