Switch to full style
For C/C++ coders discussions and solutions
Post a reply

How to implement serialization using C++ - Part 3

Sat Nov 08, 2008 10:53 pm

How to implement serialization using C++, 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.

cpp code
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);
}


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.

cpp code
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);
}


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:

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


icpp code
nt 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);
}

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 homogeous 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.


cpp code
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.

cpp code
// 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().


cpp code
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;
}

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".

cpp code
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);
}

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:
  • Generic Class Factory by Robert A. T. Káldy
  • The Factory Method (Creational) Design Pattern by Gopalan Suresh Raj
  • Abstract Factory Pattern by Mark Grand

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 (eg: 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.

clojure code
...
// 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.


Continue Reading :
c-c/serialization-implementation-in-c-part-2-t2772.html
c-c/a-serialization-primer-part-1-t2771.html



Post a reply
  Related Posts  to : How to implement serialization using C++ - Part 3
 serialization implementation in C++ - Part 2     -  
 Coding serialization concepts in C++     -  
 Part Time Online Jobs     -  
 Implement an interface in php     -  
 how to implement an interface     -  
 How to implement HttpSessionListener in JSP     -  
 best way to implement a system of name servers     -  
 How to use Generics and implement it in Java     -  
 how to implement Dynamic DDS (Domain Name System)     -  
 How to implement Command Line Parser in C++ Code     -  

Topic Tags

C++ Files and I/O