Easier XML With Boost

Introduction

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.

Serialization

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.

Archive

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:

Further Examples

italian_sandwich was a very simple example class. It is easy to handle more complicated cases, for example:

Conclusion

Hopefully these two Boost libraries can prevent us from writing some unnecessary code.  For further reference, see the Boost documentation page.