Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles / Languages / C++

Serialization implementation in C++

4.33/5 (8 votes)
26 Nov 2012CPOL3 min read 45.5K  
C++ Serialization implementation

We want our C++ objects to be persistent:

Every object should know how to save its 'state' into a file and read it when the process starts so it can continue from where it left off.

However, Serialization implementation in C++ is not trivial (unlike C#) and not all of us want to rely on

MFC framework or boost serialization (which uses templates and could result in a huge executable !).

Here are some of my points for serialization implementation:

1. You should have an abstract class for cloneable objects.

2. You should have a factory class that creates your cloneable objects given a class name

(hint :use Singleton implementation).

3. You don't have to use templates at all !

4. Automatic registration of a class into the factory isn't trivial since C++ doesn't have a static constructor.

This can be done by a macro which adds a static member into your class.

5. You should have an abstract Archive class since you don't know where you store your objects

(file, pipes, memory ..).

6. Use auto_ptr to make sure you delete your objects if the serialization has failed or an exception has been thrown.

Below is a simple but full implementation framework for serialization. Description of the files:

  • Dynamics.h - Singleton implementation of cloneable collection.
  • Persistent.h/.cpp - Serialization implementation which uses Dynamics.h.

Dynamics.h :

We start by declaring a base class for all cloneable objects. Every cloneable class should be derived from 'Clonable' and implement createObj method. createObj should return a new object of your class.

C++
class Clonable
{
public:
    virtual ~Clonable() {}  

    virtual Clonable* createObj() const = 0;
};

We should declare a collection of cloneable objects. Given a name, we should be able to create an instance of every cloneable class.

For example:

C++
string className = "MyComplexClass";

Clonable* instance = Clonables::Instance().create(className);

MyComplexClass* pCmplx = dynamic_cast<MyComplexClass*>(instance);

Below is a our cloneable collection class. Please notice the Singleton implementation.

C++
class Clonables {
private:        
    typedef map<string, const Clonable*> NameToClonable;
    NameToClonable __clonables;
private:
    Clonables() {}
    Clonables(const Clonables&);                 // Prevent copy-construction
    Clonables& operator=(const Clonables&);      //  Prevent assignment
    ~Clonables()
    {
        for(NameToClonable::const_iterator it = __clonables.begin(); it != __clonables.end(); it++){
            const Clonable* clone = it->second;
            delete clone;
        }
        __clonables.clear();
    }
public:
    static Clonables& Instance()
    {
        static Clonables instance;   // Guaranteed to be destroyed.                              
        return instance;    // Instantiated on first use.
    }
public:
    void addClonable(const char* className, const Clonable* clone)
    {
        string name = className;
        NameToClonable::const_iterator it = __clonables.find(name);
        if(it == __clonables.end()) {
            __clonables[name] = clone;
        }
    }
    Clonable* create(const char *className)
    {       
        string name = className;
        NameToClonable::const_iterator it = __clonables.find(name);
        if(it == __clonables.end()) return NULL;

        const Clonable* clone = it->second; 

        return clone->createObj();
    }   
};

Cloneable derived classes can add a static member of the following class to allow registration into '<code>Clonables' (our cloneable collection).

C++
class AddClonable {
public:
    AddClonable(const char* className, const Clonable* clone){
        Clonables::Instance().addClonable(className, clone);
    }
};

Persist.h :

We are not sure where our persistent objects would be saved. So we must implement a base class for streaming (with storing and loading support). An archive can be a file or a pipe or anything which stores our object.

C++
class Archive
{
private: 
    bool _isStoring;
public:
    Archive(bool isStoring = true) : _isStoring(isStoring) {}
    virtual ~Archive() {}

    virtual void write(const void* buffer, size_t length) {}
    virtual void read(void* buffer, size_t length) {}

    Archive& operator<<(const string& str);
    Archive& operator>>(string& str);

    Archive& operator<<(int val);
    Archive& operator>>(int& val);

    bool isStoring() const { return _isStoring; }
    void setDirection(bool isStoring) { _isStoring = isStoring; }
};

Let's define a specific 'Archive' class which uses STL iostream.

C++
class ArchiveFile: public Archive
{
private:
    iostream* _stream;
public:
    ArchiveFile(iostream* stream) : _stream(stream) {}
    virtual ~ArchiveFile() {}

    virtual void write(const void *buffer, size_t length);  
    virtual void read (void* buffer, size_t length);
};

Persistent classes are derived from the below 'Persistent' class and implement the 'serialize' method.

Notice that persistent objects are also cloneables.

C++
class Persistent : public Clonable
{
public:
    virtual ~Persistent() {}
    static Persistent* load(Archive& stream);
    void store(Archive& stream) const;
protected:
    virtual void serialize(Archive& stream) {}
    virtual int version() const { return 0; }
};

We want an automatic implementation of the createObj method and an automatic registration

of our class into the cloneables collection. This can be done with the following macro declerations:

PERSISTENT_DECL macro implements the createObj method of the 'Clonable' class for us. It also adds 'AddClonable' static member to our class : this makes our persistent class register itself to the cloneable collection. This should be added to our .h class definition (see examples below).

C++
#define PERSISTENT_DECL(className) \
public: \
virtual Clonable* createObj() const \
{ \
    return new className(); \
} \
private: \
static AddClonable _addClonable;

PERSISTENT_IMPL simply initializes this static member. This should be added to our .cpp class implementation.

C++
#define PERSISTENT_IMPL(className) \
    AddClonable className::_addClonable(#className, new className());

Example of how to use: Event.h defines a simple 'Event' class which should be persistent.

C++
class Event : public Persistent { 
private:
 int _id;  
public:
 Event() : _id(0) {}
 virtual ~Event() {}
 int getId() const { return _id; } 
protected:
 virtual void serialize(Archive& stream)
 {
  if(stream.isStoring())
   stream << _id;  
  else
   stream >> _id;
 }

 PERSISTENT_DECL(Event)
};

Event.cpp

C++
#include "Event.h"

PERSISTENT_IMPL(Event)

Before we dive into '<code>Archive' and '<code>Persistent' class implementations, here is an example of how to serialize our 'Event' object into a binary file on desktop and then read it back into a new object with the same content.

C++
void serialize_example()
{
    auto_ptr<Event> event(new Event());

    fstream file("C:\\Users\\Gilad\\Desktop\\try.data",
        ios::out | ios::in | ios::binary | ios::trunc);

    ArchiveFile stream(&file);

    if(! file)	
        throw "Unable to open file for writing";

    event->store(stream);

    file.seekg(0, ios::beg);

    Event* newEvent = dynamic_cast<Event*>(Persistent::load(stream));

    event.reset(newEvent);

    file.close();
}
<event>

Persistent.cpp:

We begin our implementation with some basic 'int' and 'string' archiving:

C++
Archive& Archive::operator<<(int val)
{
     write(&val, sizeof(int));
     return *this;
}

Archive& Archive::operator>>(int& val)
{
     read(&val, sizeof(int));
     return *this;
}

Archive& Archive::operator<<(const string& str)
{ 
    int length = str.length();
    *this << length;
    write(str.c_str(), sizeof(char) * length);
    return *this;
}

Archive& Archive::operator>>(string& str)
{
    int length = -1;
    *this >> length;
    vector<char> mem(length + 1);
    char* pChars = &mem[0];
    read(pChars, sizeof(char) * length);
    mem[length] = NULL;
    str = pChars;
    return *this;
}

Now Let's add a specific STL iostream archiving implementation :

C++
void ArchiveFile::write(const void* buffer, size_t length)
{
    _stream->write((const char*)buffer,length);
    if(! *_stream)
        throw "ArchiveFile::write Error";
}

void ArchiveFile::read(void* buffer, size_t length)
{
    _stream->read((char*)buffer, length);
    if(! *_stream)
        throw "ArchiveFile::read Error";    
}

This is how we store our persistent object into an archive :

  1. Save the object's class name.
  2. Save the version of the class.
  3. Call the object to serialize itself.
C++
void Persistent::store(Archive& stream) const 
{
    string className = typeid(*this).name();

    className = className.substr(className.find(' ') + 1);

    stream << className;

    int ver = version();

    stream << ver;

    stream.setDirection(true);

    const_cast<Persistent *>(this)->serialize(stream);
}

This is how we load an object from an archive:

  1. Read the class name from the archive
  2. Create the object using our cloneable collection and a simple cast.
  3. Make sure the version is valid.
  4. Let our object deserialize itself.

Notice the use of auto_ptr : if an exception is thrown (like from the serialize method), our persistent object would get deleted.

C++
Persistent* Persistent::load(Archive& stream)
{           
    string className;
    stream >> className;

    Clonable* clone = Clonables::Instance().create(className.c_str());
    if(clone == NULL) 
        throw "Persistent::load : Error creating object";

    auto_ptr<Clonable> delitor(clone);

    Persistent * obj = dynamic_cast<Persistent *>(clone);
    if(obj == NULL) {
        throw "Persistent::load : Error creating object";
    }

    int ver = -1;

    stream >> ver;

    if(ver != obj->version())
        throw "Persistent::load : unmatched version number";

    stream.setDirection(false);

    obj->serialize(stream);

    delitor.release();

    return obj;
}

License

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