As developers, we often want to save data from an object into a file with an XML based data format. The principle of loose coupling suggests that each object shouldn't produce its own XML code directly; there's no need for every class to know about XML. Even using an abstract representation of the XML format, such as a DOM tree, requires the class to know too much about XML. Also, writing XML is unnecessary low-level work for the developer. It's better to save to a simple, completely abstract format, and move elsewhere the details of how to save that format to XML. The Boost Serialization and Archive libraries allow this.
To add serialization support to a class, you write a serialize() method that describes in what order the data fits into a generic archive. Here's an example:
#include <list>
#include <string>
#include <boost/serialization/list.hpp>
#include <boost/serialization/string.hpp>
#include <boost/serialization/nvp.hpp>
class italian_sandwich
{
public:
italian_sandwich();
private:
string m_bread, m_cheese;
list<string> m_meats;
bool m_spicy_eggplant_p;
friend class boost::serialization::access;
template<class archive>
void serialize(archive& ar, const unsigned int version)
{
using boost::serialization::make_nvp;
ar & make_nvp("Bread", m_bread);
ar & make_nvp("Cheese", m_cheese);
ar & make_nvp("Meats", m_meats);
ar & make_nvp("Add Spicy Eggplant", m_spicy_eggplant_p);
}
}
Notes:
Note how we didn't need to manually descend into the list of strings (m_meats) and serialize each string individually. As long as a type is serializable, STL containers of that type are serialized automatically. Similarly, if we built serialization support for one of our own classes, say a bread_t class, then we could still serialize the m_bread attribute with the same simple code, instead of manually descending into the m_bread object.
To save/load a serializable object to/from an XML file, we create a file stream, initialize an XML archive with that stream, and use the << or >> operator to write the object out to the archive or read it in. Here's an example using italian_sandwich:
#include <base/file_stream.hpp>
#include <boost/archive/xml_oarchive.hpp>
#include <boost/archive/xml_iarchive.hpp>
#include <boost/serialization/nvp.hpp>
void save_sandwich(const italian_sandwich& sw, const string& file_name)
{
typedef base::file_stream bafst;
bafst::file_stream ofs(file_name, bafst::trunc | bafst::out);
boost::archive::xml_oarchive xml(ofs);
xml << boost::serialization::make_nvp("Italian Sandwich", sw);
}
italian_sandwich load_sandwich(const string& file_name)
{
typedef base::file_stream bafst;
italian_sandwich sw;
bafst::file_stream ifs(file_name, bafst::binary | bafst::in);
boost::archive::xml_oarchive xml(ifs);
xml >> boost::serialization::make_nvp("Italian Sandwich", sw);
}
Notes:
italian_sandwich was a very simple example class. It is easy to handle more complicated cases, for example:
Hopefully these two Boost libraries can prevent us from writing some unnecessary code. For further reference, see the Boost documentation page.