STL Serialization Library

A Quick User Guide

Using the STL Serialization Library (STL-SL) is a three-step process. In the first step, you declare the STL type you want to serialize using STL-SL.

value_trait < int > int_filer;

value_trait < std::list< bool > > bool_list_filer;

value_trait < std::map<std::string, std::multimap< float,
   bool > > > complex_stl_filer;

In the code snippet, three different types are declared. Each type has a varying degree of complexity. int_filer is the simplest of all, where an int is parameterized in value_trait template class. int_filer, therefore, can be used to serialize and load an intfrom a serialization file.

Similarly, the declaration of bool_list_filer prepares it for serializing a list of bool types. And complex_stl_filer prepares a map of string and multimap data types where the multimap holds float and bool pairs as shown above.

In the next step, a file_interface is created; it requires a serialization file name where the serialization data will be written to or read from.

stl_trait_writer file_interface (serialization_filename);
Note: The file_interface does not require the type during declaration. This means that STL objects of different types may be serialized in the same serialization file.

Also, because the serialization file-interface declaration is not related to the data-filer declaration, the above two steps may take each other's place.

In the third and the last stage, data is serialized into the file or loaded from it.

filer.serialize ( stl_object, file_interface );

filer.load ( stl_object, file_interface );

The following snippet illustrates the use of all three steps described above:

//declare serialization-file interfaces
stl_trait_writer file_interface ( ".\\serialization-file.txt");

//declare the STL object
std::vector < int > stl_object;


//declare the data filer 
value_trait < std::vector < int > > data_filer;


//...populate data in stl_object...//
//serialize data in stl_object in the file pointed by file_interface
data_filer.serialize ( stl_object, file_interface );

//-- or --//

//load data in the file pointed by file_interface in stl_object
//STL object
data_filer.load ( stl_object, file_interface );
//...use data in stl_object...//

STL Serialization Library

This article presents a set of template classes capable of serializing the STL objects in the user-defined file format (by default, a text file).

The STL Serialization Library is composed of two major parts: Serialization Filer and Serialization Template Classes.

Serialization Filer

The Serialization Filer consists of the following three classes, as shown in the following code:

//Serialization-file writer class.
//This guy writes the data to the file specified by 'file_path'.
//NOTE: This class does not recognize the data objects containing
//spaces, tabs, or new-line characters in them. This may be fixed
//by overloading the '<<' operator and adding
//escape-sequencing logic to it.
class stl_trait_writer: public std::ofstream
{
public:
   stl_trait_writer(const std::string& file_path)
      :std::ofstream(file_path.c_str())
   {}
};

//Serialization-file reader class.
//This guy reads the data from the file specified by 'file_path'.
//NOTE: This class does not recognize the data objects containing
//spaces, tabs, or new-line characters in them. This may be fixed
//by overloading the '<<' operator and adding
//escape-sequencing logic to it.
class file_trait_reader: public std::ifstream
{
public:
   file_trait_reader(const std::string& file_path)
      :std::ifstream(file_path.c_str())
   {}
};

//Serialization filer class.
//This guy presents the set of reader/writer objects responsible
//for reading and writing to the serialization file.
template <class writer_trait , class reader_trait>
class filer_trait
{
public:
   typedef typename writer_trait writer_type;
   typedef typename reader_trait reader_type;
};

The stl_trait_writer and file_trait_reader classes provide a basic file I/O mechanism. The filer_trait class is a filer that only pairs the above two class types. An alternate approach may be where the filer_trait class implements the file I/O mechanism for simplicity at the cost of scalability.

The file writer class (stl_trait_writer) may be replaced with a user class to change the serialization file format to, say for example, XML. A file reader class, provided for loading the serialization data from a file (file_trait_writer), will have to be altered to a class capable of understanding the user file format.

Serialization Template Classes

The set of classes in this module comprises the language mechanism to break down the complex STL types into basic types and serialize/de-serialize the basic data types.

//Basic datatype serializer class.
//Triggers the read or write to the serialization file for the
//basic datatypes.
//NOTE: This class has been tweaked to work with the
//'stl_trait_writer' class.
template <class val_trait, class val_filer_trait =
   filer_trait<stl_trait_writer, file_trait_reader> >
class value_trait
{
public:
   typedef typename val_filer_trait::writer_type writer_trait;
   typedef typename val_filer_trait::reader_type reader_trait;

   void serialize(const val_trait& val, writer_trait &pen)
   {
      pen << val << "\n";    //a tweak for 'stl_trait_writer'
                             //class defined above.
      //pen << val;          //correct code, this should replace
                             //above line of code should you
                             //choose to implement your own
                             //'stl_trait_writer' class.

      pen.flush();
   }

   void load(val_trait& val, reader_trait &pen)
   {
      pen >> val;
   }
};

The value_trait class is responsible for serializing and loading the basic data types. The code shown above is tweaked to be used with the Serialization Filer Classes, visited in the previous section.

The following excerpt illustrates the code that breaks down the complex STL types into their primitive components.

//Sequence-list datatype serializer class.
//Triggers the read or write to the serialization file for the
//Sequence-list datatypes.
//This class takes care of STL types -- list, vector, stack, queue,
//deque, and priority_queue
//NOTE: 'basic_string' type is not treated as sequence-list, but as
//a basic type.
template <class sequence_list_type, class val_filer_trait >
class sequence_list_value_trait
{
public:
   typedef typename val_filer_trait::writer_type writer_trait;
   typedef typename val_filer_trait::reader_type reader_trait;

   typedef typename sequence_list_type::size_type size_type;   
   typedef typename sequence_list_type::value_type value_type;

   void serialize (sequence_list_type& val, writer_trait &pen )
   {
      value_trait<size_type, val_filer_trait> size_filer;
      size_filer.serialize (val.size(), pen);

      for(sequence_list_type::iterator i=val.begin();
          i != val.end(); i++)
      {
         value_trait<value_type, val_filer_trait>
            val_trait_key_filer;

         val_trait_key_filer.serialize(*i,pen);
      }
   }

   void load (sequence_list_type& val, reader_trait &pen )
   {
      value_trait<size_type, val_filer_trait> size_reader;
      size_type val_size=0;
      size_reader.load(val_size, pen);

      for(; val_size > 0; val_size--)
      {
         value_type element;

         value_trait<value_type, val_filer_trait>
            val_trait_key_reader;

         val_trait_key_reader.load(element, pen);

         val.push_back(element);
      }
   }
};

STL Serialization Library

The sequence_list_value_trait class breaks down the list, vector, stack, queue, deque, and priority_queue STL types to their finer component types. Thus, a vector of int types will be broken down into a list of int types in the sequence iterated by the default vector::iterator type. And, the int type values are serialized by the value_trait class.

//Triggers the read or write to the serialization file for the
//Associative-list datatypes.
//This class takes care of STL types -- map, multimap, set, multiset
template <class associative_list_type, class val_filer_trait >
class associative_list_value_trait
{
public:

   typedef typename val_filer_trait::writer_type writer_trait;
   typedef typename val_filer_trait::reader_type reader_trait;

   typedef typename associative_list_type::size_type size_type;
   typedef typename associative_list_type::key_type key_type;
   typedef typename associative_list_type::mapped_type data_type;

   void serialize (associative_list_type& val, writer_trait &pen )
   {
      value_trait<size_type, val_filer_trait> size_filer;
      size_filer.serialize (val.size(), pen);

      for(associative_list_type::iterator i=val.begin();
          i != val.end(); i++)
      {
         value_trait<key_type, val_filer_trait>
            val_trait_key_filer;
         value_trait<data_type, val_filer_trait>
            val_trait_data_filer;


         val_trait_key_filer.serialize(i->first,pen);
         val_trait_data_filer.serialize(i->second,pen);
      }
   }

   void load (associative_list_type& val, reader_trait &pen )
   {
      value_trait<size_type, val_filer_trait> size_reader;
      size_type val_size=0;
      size_reader.load(val_size, pen);

      for(; val_size > 0; val_size--)
      {
         key_type key_element;
         value_trait<key_type, val_filer_trait>
            val_trait_key_reader;
         val_trait_key_reader.load(key_element, pen);

         data_type data_element;
         value_trait<data_type, val_filer_trait>
            val_trait_data_reader;
         val_trait_data_reader.load(data_element, pen);

         val.insert (std::pair<key_type, data_type>
                     (key_element, data_element));
      }
   }
};

The associative_list_value_trait class is a bit-complicated version of the sequence_list_value_trait class wherein the map, multimap, set, and multiset STL types are broken down into their key and data-element types and broken down further as required or serialized.

The following extract depicts the mechanism by which a complex STL type initiates the breakdown process using the associative_list_value_trait and sequence_list_value_trait classes covered above.

//STL vector datatype serializer class.
//Triggers the read or write to the serialization file for the
//STL vector datatype.
template <class val_trait_key, class val_trait_data,
   class val_filer_trait  >
class value_trait< std::vector<val_trait_key,
   val_trait_data> , val_filer_trait >
{
public:

   typedef typename val_filer_trait::writer_type writer_trait;
   typedef typename val_filer_trait::reader_type reader_trait;

   typedef std::vector<val_trait_key, val_trait_data>
      vector_value_trait;

   void serialize (vector_value_trait& val, writer_trait &pen )
   {
      sequence_list_value_trait<vector_value_trait,
         val_filer_trait> sequence_list_value_filer;

      sequence_list_value_filer.serialize (val, pen);
   }

   void load (vector_value_trait& val, reader_trait &pen )
   {

      sequence_list_value_trait<vector_value_trait,
         val_filer_trait> sequence_list_value_reader;

      sequence_list_value_reader.load (val, pen);
   }
};

The preceding section of code initiates the dissolution of the vector STL type into granular components for further serialization. Similar logic is implemented for list, stack, queue, deque, and priority_queue STL types.

//STL multimap datatype serializer class.
//Triggers the read or write to the serialization file for the STL
//multimap datatype.
template <class val_trait_key, class val_trait_data,
   class val_filer_trait  >
class value_trait< std::multimap<val_trait_key, val_trait_data> ,
   val_filer_trait  >
{
public:

   typedef typename val_filer_trait::writer_type writer_trait;
   typedef typename val_filer_trait::reader_type reader_trait;

   typedef std::multimap<val_trait_key, val_trait_data>
      multimap_value_trait;

   void serialize (multimap_value_trait& val, writer_trait &pen )
   {

      associative_list_value_trait<multimap_value_trait,
         val_filer_trait> associative_list_value_filer;

      associative_list_value_filer.serialize (val, pen);
   }

   void load (multimap_value_trait& val, reader_trait &pen )
   {

      associative_list_value_trait<multimap_value_trait,
         val_filer_trait> associative_list_value_reader;

      associative_list_value_reader.load (val, pen);
   }
};

Like the preceding excerpt that dealt with fragmenting the vector type to finer components, this piece of code dissolves a multimap into its key type and data-type component for further serialization. Similar logic may be implemented for other associative containers such as map, set, and multiset.

Comments

It should be noted that the library above creates a class for every sub-type in the STL filer declaration. Although this does not account for any extra runtime overhead, it doubles the number of classes that are serialized in a namespace. If the class count exceeds that supported by your compiler, you might want to re-factor the class hierarchy.

That fact may be seen as a disadvantage, but it should be noted that the class count does not exert any overhead at run time. The advantage is that STL-SL code is type safe.

STL-SL, as presented here, is the first revision; you may want to include the type information about the STL type that is being serialized in the serialization file, thus aiding run-time type checking in the STL-SL.

Other libraries, such as BOOST, are available; they are much more powerful but arguably not necessarily intuitive. These libraries address a multitude of other problems such as class versioning, pointer restoration, and data portability. The purpose of this article is not to replace them; this article should be seen as a lightweight, easy-to-use alternative. Moreover, the code presented here may be viewed as an STL serialization engine for an even fuller implementation of a Serialization Library.



Downloads

Comments

  • There are no comments yet. Be the first to comment!

Leave a Comment
  • Your email address will not be published. All fields are required.

Top White Papers and Webcasts

  • On-demand Event Event Date: September 10, 2014 Modern mobile applications connect systems-of-engagement (mobile apps) with systems-of-record (traditional IT) to deliver new and innovative business value. But the lifecycle for development of mobile apps is also new and different. Emerging trends in mobile development call for faster delivery of incremental features, coupled with feedback from the users of the app "in the wild." This loop of continuous delivery and continuous feedback is how the best mobile …

  • Packaged application development teams frequently operate with limited testing environments due to time and labor constraints. By virtualizing the entire application stack, packaged application development teams can deliver business results faster, at higher quality, and with lower risk.

Most Popular Programming Stories

More for Developers

Latest Developer Headlines

RSS Feeds