WEBINAR:On-Demand

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.

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:

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:

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.

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.

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:

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
}
R3DModel::~R3DModel()
//
// 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!!!
//
{
m_ShapeList.Serialize(ar);
}

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

Advertiser Disclosure:
Some of the products that appear on this site are from companies from which QuinStreet receives compensation. This compensation may impact how and where products appear on this site including, for example, the order in which they appear. QuinStreet does not include all companies or all types of products available in the marketplace.

Thanks for your registration, follow us on our social networks to keep up-to-date