Click here to Skip to main content
15,885,244 members
Articles / Programming Languages / C++
Tip/Trick

Serialization implementation in C++

Rate me:
Please Sign up or sign in to vote.
4.33/5 (8 votes)
26 Nov 2012CPOL3 min read 44.7K   25   4
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)


Written By
Software Developer
Israel Israel
This member has not yet provided a Biography. Assume it's interesting and varied, and probably something to do with programming.

Comments and Discussions

 
GeneralMy vote of 5 Pin
noname199016-Apr-14 23:08
noname199016-Apr-14 23:08 
GeneralExcellent (5 Stars) with minor suggestion Pin
jmclaine28-Sep-13 17:27
jmclaine28-Sep-13 17:27 
QuestionGood Pin
Sajeesh Payolam24-Dec-12 22:13
Sajeesh Payolam24-Dec-12 22:13 
GeneralMy vote of 5 Pin
avnerma13-Dec-12 7:42
avnerma13-Dec-12 7:42 
Great article !!!

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.