Click here to Skip to main content
15,867,983 members
Articles / Desktop Programming / MFC

A Serialization Primer - Part 3

Rate me:
Please Sign up or sign in to vote.
4.76/5 (39 votes)
23 Jun 2005CPOL5 min read 256.1K   82   77
This tutorial describes how to serialize complex objects.

This article is the third of a 3 part tutorial on serialization.

  • Part 1 introduces the basics of serialization.
  • Part 2 explains how to gracefully handle reading invalid data stores and support versioning.
  • Part 3 describes how to serialize complex objects.

In the previous two parts, we learned how to provide robust support for serialization in general. In this article, we'll learn the specific rules for serializing any kind of object. There are 4 general cases to consider. Each case builds on the previous one.

  • Serializing a simple class
  • Serializing a derived class
  • Serializing a homogenous collection class
  • Serializing a heterogenous collection class

Our serialize() method will return one of these status codes:

  • Success
  • InvalidFormat
  • UnsupportedVersion
  • ReadError
  • WriteError

Serializing a Simple Class

A "simple class" is defined as an object that has no parent class and is not a collection class. To serialize a simple class, do the following:

  1. Serialize the object's signature and version
  2. Serialize the object's members (if any)

In the following example, the class Point contains 2 int members that represent the coordinates of a point. The object's signature and version are defined as static members (m_strSignature and m_nVersion), since they apply to all instances of Point.

C++
int Point::serialize
  (CArchive* pArchive)
{
  ASSERT (pArchive != NULL);

  // Step 1: Serialize signature and version
  int nVersion;
  try {
    if (pArchive->IsStoring()) {
        (*pArchive) << Point::m_strSignature;
        (*pArchive) << Point::m_nVersion;
    } else {
        CString strSignature;
        (*pArchive) >> strSignature;
        if (strSignature != Point::m_strSignature)
           return (Status::InvalidFormat);
        (*pArchive) >> nVersion;
        if (nVersion > Point::m_nVersion;)
           return (Status::UnsupportedVersion);
    }

    // Step 2: Serialize members
    if (pArchive->IsStoring()) {
        (*pArchive) << m_nX;
        (*pArchive) << m_nY;
    } else {
        (*pArchive) >> m_nX;
        (*pArchive) >> m_nY;
    }
  }
  catch (CException* pException) {
    // A read/write error occurred
    pException->Delete();
    if (pArchive->IsStoring())
      return (Status::WriteError);
    return (Status::ReadError);
  }

  // Object was successfully serialized
  return (Status::Success);
}

Serializing a Derived Class

For the purpose of this discussion, a derived class is one that is derived from a simple class and is not a collection class. To serialize a derived class, do the following:

  1. Serialize the object's signature and version
  2. Serialize the object's base class << additional step
  3. Serialize the object's members (if any)

In the following example, the class ColoredPoint is derived from Point and contains an additional int member called m_nColor, which specifies the point's color. Like all serializable classes, ColoredPoint also defines a static signature and version.

C++
int ColoredPoint::serialize
  (CArchive* pArchive)
{
  ASSERT (pArchive != NULL);

  // Step 1: Serialize signature and version
  int nVersion;
  try {
    if (pArchive->IsStoring()) {
        (*pArchive) << ColoredPoint::m_strSignature;
        (*pArchive) << ColoredPoint::m_nVersion;
    } else {
        CString strSignature;
        (*pArchive) >> strSignature;
        if (strSignature != ColoredPoint::m_strSignature)
           return (Status::InvalidFormat);
        (*pArchive) >> nVersion;
        if (nVersion > ColoredPoint::m_nVersion;)
           return (Status::UnsupportedVersion);
    }

    // Step 2: Serialize the base class
    int nStatus = Point::serialize (pArchive);
    if (nStatus != Status::Success)
       return (nStatus);

    // Step 3: Serialize members
    if (pArchive->IsStoring())
       (*pArchive) << m_nColor;
    else
       (*pArchive) >> m_nColor;
  }
  catch (CException* pException) {
    // A read/write error occurred
    pException->Delete();
    if (pArchive->IsStoring())
      return (Status::WriteError);
    return (Status::ReadError);
  }

  // Object was successfully serialized
  return (Status::Success);
}

Serializing a Homogenous Collection Class

Homogenous collection classes are used to store dynamically sized collections of the same type of object. To serialize a homogenous collection class, do the following:

  1. Serialize the object's signature and version
  2. Serialize the object's base class (if any)
  3. Serialize the number of items in the collection << additional step
  4. Serialize each object in the collection << additional step
  5. Serialize the object's other members (if any)

In the following example, the class ColoredPointList is a collection of ColoredPoint objects. To keep things simple, ColoredPointList uses a CPtrArray to store objects. Like all serializable classes, ColoredPointList also defines a static signature and version. Here's what ColoredPointList looks like:

C++
class ColoredPointList
{
  // Construction/destruction
  public:
    ColoredPointList::ColoredPointList();
    virtual ColoredPointList::~ColoredPointList();

  // Attributes
  public:
    static const CString m_strSignature;
    static const int m_nVersion;

  // Operations
  public:
    int serialize (CArchive* pArchive);

  // Members
  protected:
    CPtrArray m_coloredPoints;
}

And here's how we serialize it:

C++
int ColoredPointList::serialize
  (CArchive* pArchive)
{
  ASSERT (pArchive != NULL);
  int nStatus = Status::Success;

  // Step 1: Serialize signature and version
  int nVersion;
  try {
    if (pArchive->IsStoring()) {
        (*pArchive) << ColoredPointList::m_strSignature;
        (*pArchive) << ColoredPointList::m_nVersion;
    } else {
        CString strSignature;
        (*pArchive) >> strSignature;
        if (strSignature != ColoredPointList::m_strSignature)
           return (Status::InvalidFormat);
        (*pArchive) >> nVersion;
        if (nVersion > ColoredPointList::m_nVersion;)
           return (Status::UnsupportedVersion);
    }

    // Step 2: Serialize base class (if any)
    //
    // Nothing to do since ColoredPointList isn't derived from anything.
    // But if it was derived from BaseColoredPointList, we'd do:
    //
    // nStatus = BaseColoredPointList::serialize (pArchive);
    // if (nStatus != Status::Success)
    //    return (nStatus);

    // Step 3: Serialize number of items in collection
    int nItems = 0;
    if (pArchive->IsStoring()) {
         nItems = m_coloredPoints.GetSize();
         (*pArchive) << nItems;
    } else
         (*pArchive) >> nItems;

    // Step 4: Serialize each object in collection
    for (int nObject=0; (nObject < nItems); nObject++) {

        // 4a: Point to object being serialized
        ColoredPoint* pColoredPoint = NULL;
        if (pArchive->IsStoring())
           pColoredPoint = (ColoredPoint *) m_coloredPoints.GetAt (nObject);
        else
           pColoredPoint = new ColoredPoint();
        ASSERT (pColoredPoint != NULL);

        // 4b: Serialize it
        nStatus = pColoredPoint->serialize (pArchive);
        if (nStatus != Status::Success)
           return (nStatus);
        if (!pArchive->IsStoring())
           m_coloredPoints.Add (pColoredPoint);
    }

    // Step 5: Serialize object's other members (if any)
    //
    // Nothing to do since ColoredPointList doesn't have any other
    // members. But if it contained an int (m_nSomeInt) and a Foo
    // object (m_foo), we'd do:
    //
    // if (pArchive->IsStoring())
    //    (*pArchive) << m_nSomeInt;
    // else
    //    (*pArchive) >> m_nColor;
    //
    // nStatus = m_foo::serialize (pArchive);
    // if (nStatus != Status::Success)
    //    return (nStatus);

  }
  catch (CException* pException) {
    // A read/write error occurred
    pException->Delete();
    if (pArchive->IsStoring())
      return (Status::WriteError);
    return (Status::ReadError);
  }

  // Object was successfully serialized
  return (Status::Success);
}

Serializing a Heterogenous Collection Class

Heterogenous collection classes are used to store dynamically sized collections of potentially different types of objects. To serialize a heterogenous collection class, do the following:

  1. Serialize the object's signature and version
  2. Serialize the object's base class (if any)
  3. Serialize the number of items in the collection
  4. For each object in the collection
    1. serialize that object's signature << additional step
    2. then serialize that object
  5. Serialize the object's other members (if any)

You'll notice the only additional step in serializing heterogenous collections is 4(a), where we serialize each object's signature before serializing the object itself. This comes in handy when reading back our data. When we serialized a homogenous collection, we dealt with objects of the same type (ColoredPoints in the previous example). To read in a ColoredPoint, we constructed it on the heap and then called its serialize() method.

C++
ColoredPoint* pColoredPoint = new ColoredPoint();
nStatus = pColoredPoint->serialize (pArchive);

When we're dealing with heterogenous collections, we need to know the type of object we're reading back in, before actually serializing it. That's where the object's signature comes in. Since we saved the signature on the way out, we can construct an object of the appropriate type on the way in.

C++
// Read object signature
CString strSignature;
pArchive >> strSignature;

// Construct object of appropriate type
ISerializable* pObject = NULL;
if (strSignature == ColoredPoint::m_strSignature)
   pObject = new ColoredPoint();
else
  if (strSignature == Line::m_strSignature)
     pObject = new Line();
  else
     if (strSignature == Rectangle::m_strSignature)
        pObject = new Rectangle();
     else
        return (Status::InvalidFormat);
ASSERT (pObject != NULL);

// Read it back in
nStatus = pObject->serialize (pArchive);

In the above code fragment, ColoredPoint, Line and Rectangle are all (eventually) derived from a common base class ISerializable, which is nothing more than an abstract base class containing only pure virtual methods (in other words, an "interface"). ISerializable defines the methods getSignature(), getVersion() and serialize().

C++
class ISerializable
{
  // Construction/destruction
  public:
    ISerializable::ISerializable()
      { }
    virtual ISerializable::~ISerializable()
      { }

  // Operations
  public:
    // Get the object's signature
    virtual CString getSignature() = 0;

    // Get the object's version
    virtual int getVersion() = 0;

    // Serialize the object
    virtual int serialize (CArchive* pArchive) = 0;
}

OK, let's serialize our heterogenous collection. In the following example, the class ShapeList is a collection of varying numbers of ColoredPoint, Line and Rectangle objects, all of which derive from ISerializable. You can look upon these classes as "implementing the ISerializable interface".

C++
int ShapeList::serialize
  (CArchive* pArchive)
{
  ASSERT (pArchive != NULL);
  int nStatus = Status::Success;

  // Step 1: Serialize signature and version
  int nVersion;
  try {
    if (pArchive->IsStoring()) {
        (*pArchive) << ShapeList::m_strSignature;
        (*pArchive) << ShapeList::m_nVersion;
    } else {
        CString strSignature;
        (*pArchive) >> strSignature;
        if (strSignature != ShapeList::m_strSignature)
           return (Status::InvalidFormat);
        (*pArchive) >> nVersion;
        if (nVersion > ShapeList::m_nVersion;)
           return (Status::UnsupportedVersion);
    }

    // Step 2: Serialize base class (if any)
    //
    // Nothing to do since ShapeList isn't derived from anything.
    // But if it was derived from BaseShapeList, we'd do:
    //
    // nStatus = BaseShapeList::serialize (pArchive);
    // if (nStatus != Status::Success)
    //    return (nStatus);

    // Step 3: Serialize number of items in collection
    int nItems = 0;
    if (pArchive->IsStoring()) {
         nItems = m_shapes.GetSize();
         (*pArchive) << nItems;
    } else
         (*pArchive) >> nItems;

    // Step 4: Serialize each object in collection
    for (int nObject=0; (nObject < nItems); nObject++) {

        // 4a: First serialize object's signature
        CString strSignature;
        if (pArchive->IsStoring())
           (*pArchive) << pObject->getSignature();
        else
           (*pArchive) >> strSignature;

        //
        // 4b: Then serialize object
        //

        // 4b (1): Point to object being serialized
        ISerializable* pObject = NULL;
        if (pArchive->IsStoring())
           pObject = (ISerializable *) m_shapes.GetAt (nObject);
        else {
           if (strSignature == ColoredPoint::m_strSignature)
              pObject = new ColoredPoint();
           else
             if (strSignature == Line::m_strSignature)
                pObject = new Line();
             else
                if (strSignature == Rectangle::m_strSignature)
                   pObject = new Rectangle();
                else
                   return (Status::InvalidFormat);
        }
        ASSERT (pObject != NULL);

        // 4b (2): Serialize it
        nStatus = pObject->serialize (pArchive);
        if (nStatus != Status::Success)
           return (nStatus);
        if (!pArchive->IsStoring())
           m_shapes.Add (pObject);
    }

    // Step 5: Serialize object's other members (if any)
    //
    // Nothing to do since ShapeList doesn't have any other
    // members.  But if it contained an int (m_nSomeInt) and
    // a Foo object (m_foo), we'd do:
    //
    // if (pArchive->IsStoring())
    //    (*pArchive) << m_nSomeInt;
    // else
    //    (*pArchive) >> m_nColor;
    //
    // nStatus = m_foo::serialize (pArchive);
    // if (nStatus != Status::Success)
    //    return (nStatus);

  }
  catch (CException* pException) {
    // A read/write error occurred
    pException->Delete();
    if (pArchive->IsStoring())
      return (Status::WriteError);
    return (Status::ReadError);
  }

  // Object was successfully serialized
  return (Status::Success);
}

Class Factory

You can replace the ugly if statement in the code fragment by using a "class factory" to serve up a new instance of a class based on its signature. Here are some articles you might want to look at:

Although the articles vary in complexity, the basic idea behind them is the same. A class factory is little more than a class that offers an appropriately named static method (e.g.: create()) that serves up an object of a specific type. You could hide the nasty if statement in the factory's create() method, cleaning up the code a bit.

C++
...
// Construct object of appropriate type
ISerializable* pObject = MyClassFactory::create (strSignature);
ASSERT (pObject != NULL);
...

Conclusion

Although this primer refers to MFC objects like CString and CPtrArray, serialization is not specific to MFC. It's a common operation performed by any software that needs to save and restore information to and from persistent storage.

Revision History

  • 24 Jun 2005
    Fixed typo in example code (thanks, J Roy!)
  • 13 Jun 2005
    Fixed typos in example code (thanks, Mohammed Tarik!)
  • 26 Apr 2004
    Fixed typo in example code (thanks, David Paul Jones!)
  • 24 Apr 2004
    Added much needed examples
  • 18 Feb 2002
    Initial version

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
Technical Lead
Canada Canada
Ravi Bhavnani is an ardent fan of Microsoft technologies who loves building Windows apps, especially PIMs, system utilities, and things that go bump on the Internet. During his career, Ravi has developed expert systems, desktop imaging apps, marketing automation software, EDA tools, a platform to help people find, analyze and understand information, trading software for institutional investors and advanced data visualization solutions. He currently works for a company that provides enterprise workforce management solutions to large clients.

His interests include the .NET framework, reasoning systems, financial analysis and algorithmic trading, NLP, HCI and UI design. Ravi holds a BS in Physics and Math and an MS in Computer Science and was a Microsoft MVP (C++ and C# in 2006 and 2007). He is also the co-inventor of 3 patents on software security and generating data visualization dashboards. His claim to fame is that he crafted CodeProject's "joke" forum post icon.

Ravi's biggest fear is that one day he might actually get a life, although the chances of that happening seem extremely remote.

Comments and Discussions

 
GeneralMy vote of 5 Pin
peoria12325-Jan-13 12:13
peoria12325-Jan-13 12:13 
QuestionSerializing Clistcontrol Pin
peoria12325-Jan-13 12:11
peoria12325-Jan-13 12:11 
QuestionDoes it support multi-inheritance? Pin
transoft18-May-09 2:04
transoft18-May-09 2:04 
AnswerRe: Does it support multi-inheritance? Pin
Ravi Bhavnani18-May-09 5:46
professionalRavi Bhavnani18-May-09 5:46 
GeneralRe: Does it support multi-inheritance? Pin
transoft18-May-09 5:54
transoft18-May-09 5:54 
GeneralNice articles. Pin
quakeboy28-Nov-08 11:48
quakeboy28-Nov-08 11:48 
AnswerRe: Nice articles. Pin
Ravi Bhavnani28-Nov-08 11:52
professionalRavi Bhavnani28-Nov-08 11:52 
GeneralRe: Nice articles. Pin
quakeboy29-Nov-08 6:51
quakeboy29-Nov-08 6:51 
GeneralRe: Nice articles. Pin
Ravi Bhavnani29-Nov-08 7:27
professionalRavi Bhavnani29-Nov-08 7:27 
GeneralRe: Nice articles. Pin
quakeboy29-Nov-08 10:30
quakeboy29-Nov-08 10:30 
GeneralRe: Nice articles. Pin
Ravi Bhavnani30-Nov-08 7:23
professionalRavi Bhavnani30-Nov-08 7:23 
GeneralI have found for moths,thank you very much!!! Pin
thenewhd16-Dec-07 20:04
thenewhd16-Dec-07 20:04 
GeneralAfter all these years, i got it!! Pin
pointer17-Nov-07 7:17
pointer17-Nov-07 7:17 
GeneralRe: After all these years, i got it!! Pin
Ravi Bhavnani17-Nov-07 7:21
professionalRavi Bhavnani17-Nov-07 7:21 
QuestionWhen and where CArchive& AFXAPI operator>>(CArchive& ar,class_name* &pOb) is called Pin
xxq3121716-Apr-07 15:48
xxq3121716-Apr-07 15:48 
AnswerRe: When and where CArchive& AFXAPI operator>>(CArchive& ar,class_name* &pOb) is called Pin
xxq3121717-Apr-07 15:28
xxq3121717-Apr-07 15:28 
GeneralSome remarks (or bug fixes) Pin
cristitomi14-Feb-07 1:49
cristitomi14-Feb-07 1:49 
GeneralRe: Some remarks (or bug fixes) Pin
cristitomi14-Feb-07 2:37
cristitomi14-Feb-07 2:37 
GeneralVery nice article indeed Pin
cristitomi13-Feb-07 5:33
cristitomi13-Feb-07 5:33 
GeneralRe: Very nice article indeed Pin
Ravi Bhavnani13-Feb-07 5:44
professionalRavi Bhavnani13-Feb-07 5:44 
GeneralCArray Object Serialization Pin
murtaza dhari31-Aug-06 9:45
murtaza dhari31-Aug-06 9:45 
AnswerRe: CArray Object Serialization Pin
Ravi Bhavnani31-Aug-06 9:51
professionalRavi Bhavnani31-Aug-06 9:51 
GeneralRe: CArray Object Serialization Pin
murtaza dhari1-Sep-06 7:33
murtaza dhari1-Sep-06 7:33 
QuestionHow to implement versioning for CDocument Pin
justsuraj30-Jul-06 21:02
justsuraj30-Jul-06 21:02 
AnswerRe: How to implement versioning for CDocument Pin
Ravi Bhavnani31-Jul-06 2:04
professionalRavi Bhavnani31-Jul-06 2:04 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.