![]() |
Languages »
C / C++ Language »
Beginners
Beginner
License: The Code Project Open License (CPOL)
A serialization primer - Part 3By Ravi BhavnaniThis tutorial describes how to serialize complex objects. |
VC6, VC7Win2K, WinXP, Visual Studio, MFC, Dev
|
|
Advanced Search |
|
|
|
||||||||||||||||
This article is the third of a 3 part tutorial on serialization.
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.
serialize() method will return one of these status codes:
Success InvalidFormat UnsupportedVersion ReadError WriteError 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.
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 occured pException->Delete(); if (pArchive->IsStoring()) return (Status::WriteError); return (Status::ReadError); } // Object was successfully serialized return (Status::Success); }
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:
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.
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 occured pException->Delete(); if (pArchive->IsStoring()) return (Status::WriteError); return (Status::ReadError); } // Object was successfully serialized return (Status::Success); }
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:
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:
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:
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 occured pException->Delete(); if (pArchive->IsStoring()) return (Status::WriteError); return (Status::ReadError); } // Object was successfully serialized return (Status::Success); }
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:
ColoredPoints in the previous example). To read in a ColoredPoint, we constructed it on the heap and then called its serialize() method.
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.
// 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().
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".
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 occured pException->Delete(); if (pArchive->IsStoring()) return (Status::WriteError); return (Status::ReadError); } // Object was successfully serialized return (Status::Success); }
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:
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.
... // Construct object of appropriate type ISerializable* pObject = MyClassFactory::create (strSignature); ASSERT (pObject != NULL); ...
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.
General
News
Question
Answer
Joke
Rant
Admin
|
PermaLink |
Privacy |
Terms of Use
Last Updated: 24 Jun 2005 Editor: Sean Ewington |
Copyright 2002 by Ravi Bhavnani Everything else Copyright © CodeProject, 1999-2009 Web12 | Advertise on the Code Project |