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

  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 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:

  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

  1. 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 from CObject. A CObList collection is defined to hold pointers to CObject. When you retrieve an object from the list, you may have to cast the result to the proper type since the CObList functions return pointers to CObject. For example, if you store CPerson objects in a CObList collection, you have to cast a retrieved element to be a pointer to a CPerson object. The following example uses a CObList collection to hold CPerson 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

  1. 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 hold CPerson objects, you might add the wrapper functions AddHeadPerson and GetHeadPerson, 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 the GetHeadPerson 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.

See also

Collections