Serializable Polymorphic Lists

This tutorial stems from some of my earliest experiences trying to get a bunch of objects onto disk and back again.  Having tried various complicated approaches I finally stumbled on the correct way to do it.  The aim is to have a list of objects all derived from some common base class that can be saved to disk.  This is obviously a very common task in any program.

Step 1:   Derive your classes from CObject

CObject can be a pain because it defines a private copy constructor which can mean you may have to write your own copy constructor for each class (don't worry, if you use the CTypedPtrList template as I do in this example you seem to be able to get away without it).  However, the advantages of the run-time type information provided by CObject far outweigh this.  Our example will use objects from a simple 3D engine.  All the classes are derived from R3DShape which is in turn derived from CObject.

  class R3DShape : public CObject
    DECLARE_SERIAL(R3DShape) // required by MFC

    R3DShape(); // default constructor
    virtual void Draw(); // an overrideable draw function
    virtual void Serialize(CArchive &ar); // required by MFC

    float m_number;
    R3DVector m_position;

  class R3DTriangle : public R3DShape

    void SetupTriangle(R3DVector v1...);
    virtual void Serialize(CArchive &ar);

    float m_amembervariable;

  class R3DRectangle : public R3DShape


  class R3DCube : public R3DShape { etc etc };

  class R3DSpecialCube : public R3DCube { etc etc };

Step 2:   Use the IMPLEMENT_SERIAL macro

I remember reading one of Bjarne Stroustrup's books in which he said that macros and #defines were unnecessary and generally a bad idea in C++.  Still, Microsoft have insisted on using them anyway, and they do seem to make life easier.  The IMPLEMENT_SERIAL macro takes three arguments: a class, the name of it's base class, and a version number.  The version number is so that you can make sure you don't try to load files created with old versions of your software.  The implementation for R3DShape would look something like this:

  IMPLEMENT_SERIAL(R3DShape, CObject, 1)

    // initialisation code here
The other classes must also have the macro.  It is important that you put the macro in the class's *.cpp file and not the *.h file.  The other calls to the macro (for the classes in step 1) will look like this:
  IMPLEMENT_SERIAL(R3DTriangle, R3DShape, 1)
  IMPLEMENT_SERIAL(R3DSpecialCube, R3DCube, 1)

Step 3:   Implement Serialize() functions

Each class must implement its own Serialize function.  A reference to a CArchive object is the only parameter.  CArchives are either passed in by the framework when the user clicks "save" or "open", or you create them yourself (having first created a CFile) object.

  void R3DShape::Serialize(CArchive &ar)
    CObject::Serialize(ar);    // ALWAYS call the base class version 1st!
    m_Position.Serialize(ar);  // because m_Position is a CObject
    if (ar.IsStoring())
       ar << m_number;
       ar >> m_number;
There are two important points here.  First, always call the base class version of Serialize().  This ensures that all data is saved.  When CObject::Serialize() is called, the run-time class information is saved so that when you load up again you get an object of the correct class.

This leads on to the second point, which involves the special consideration required when serializing classes derived from CObject.  Basically, if the exact type of the object is known and its memory has already been allocated (either with new or because it's an embedded object) then we use the object.Serialize(ar) method.  If we haven't allocated memory yet and we know that the object we're loading is either of class X or is derived from X, then we use the << and >> operators.  This is because << and >> on a CObject first figure out the class of the object we're loading and then allocate memory before returning a pointer.  All this makes our job a lot easier.

This is what the Serialize method for R3DSpecialCube might look like:

  void R3DSpecialCube::Serialize (CArchive &ar)
    R3DCube::Serialize (ar);   // call base class version
    m_pData1->Serialize (ar);
    m_Data2.Serialize (ar);
    if (ar.IsStoring())
       ar << m_D1 << m_D2 << m_D3;
       ar >> m_D1 >> m_D2 >> m_D3;
Here, m_pData1 is a pointer to a CObject whose memory was already allocated, probably in R3DSpecialCube's constructor.  m_Data2 is an embedded object.  m_D1 might be some data of a standard type or it might be a CObject whose memory has not been allocated yet.

Step 4:   Use the CTypedPtrList template

My first impulse was to use CLists and CArrays for everything.  But they caused major headaches and after reading the Scribble tutorial I discovered that CTypedPtrList was a much simpler way of doing things.  Without going into too much detail, we create a list of R3DShapes like this:

  CTypedPtrList mylist;
This will give you a list of pointers to R3DShapes, which may be R3DShapes or any class derived from R3DShape like R3DTriangle or R3DSpecialCube.  This is where the polymorphism comes in!  There are various things you might want to do with a list.  In our example, we have a class R3DModel which contains a list of R3DShapes:
  class R3DModel : public CObject


      virtual void Serialize (CArchive &ar);
      R3DShape* AddShape();

      CTypedPtrList m_Shapelist;
You'll notice this is very similar to the CScribbleDoc class in the Scribble tutorial.  Basically, R3DModel manages this list.  The various implementations look something like this:

  R3DShape* R3DModel::AddShape()
  // This creates a new R3DShape and adds it to the list:
    R3DShape *newshape = new R3DShape(); // allocate the memory
    m_ShapeList.AddTail (newshape);      // add it to the list
    return newshape;                    // so that the creating object can access it

  // Destructor deletes all the items in the list:
    while (!m_Shapelist.IsEmpty())
          delete m_ShapeList.RemoveHead();

  void R3DModel::Serialize (CArchive &ar)
  // The all important serialize function!!!
That really is all there is to it.  The single line will take care of serializing each and every item in the list.  Every item's Serialize() member will be called, and it doesn't matter how far down the class hierarchy an object is because every object calls its base class's Serialize() function.  At the top of the heirarchy lies CObject::Serialize() which takes care of saving and loading the class information.  So we can now have as many different kinds of R3DShape as we like, and the R3DModel class doesn't need to know in advance what kinds of R3DShape there are going to be.

Further Reading

I highly recommend the Scribble tutorial in the online documentation.  Most of what I've covered (except the polymorphism) is demonstrated in

  Developer Products
    \Visual C++
       \Visual C++ Tutorials
          \Scribble:MDI Drawing Application
             \Creating the Document


  • unexpected end of file??

    Posted by Legacy on 09/03/2003 12:00am

    Originally posted by: mamtha

    I need to update the file-format for my MFC app since the next format was incompatble with the old one.
    it gives unexpected file format exception.
    how do i overcome this?

  • Serialization in application in dialog based application?

    Posted by Legacy on 06/26/1999 12:00am

    Originally posted by: Vitaly A.Kaluzhny

    How can I manage to serialize object lists in dialog
    based application (InstallShiled like Wizard)?

Leave a Comment
  • Your email address will not be published. All fields are required.

Top White Papers and Webcasts

  • Live Event Date: December 11, 2014 @ 1:00 p.m. ET / 10:00 a.m. PT Market pressures to move more quickly and develop innovative applications are forcing organizations to rethink how they develop and release applications. The combination of public clouds and physical back-end infrastructures are a means to get applications out faster. However, these hybrid solutions complicate DevOps adoption, with application delivery pipelines that span across complex hybrid cloud and non-cloud environments. Check out this …

  • On-demand Event Event Date: October 29, 2014 It's well understood how critical version control is for code. However, its importance to DevOps isn't always recognized. The 2014 DevOps Survey of Practice shows that one of the key predictors of DevOps success is putting all production environment artifacts into version control. In this webcast, Gene Kim discusses these survey findings and shares woeful tales of artifact management gone wrong! Gene also shares examples of how high-performing DevOps …

Most Popular Programming Stories

More for Developers

RSS Feeds