1

I'm using Boost to serialize an instance of a class into an XML file. That class contains a collection that I would like to be saved into a separate binary (or ASCII) file. Ideally (but not mandatory), I'd like the XML to contain the path to that separate file. Then, in my main script, I call the static Save method to serialize the instance, and later the Load method to deserialize it.

Here is an example of what I would like to do:

class MyClass
{
  public:

     ...

     friend class boost::serialization::access;
     template<class Archive>
     void serialize(Archive& ar, const unsigned int version)
     {
         ar & BOOST_SERIALIZATION_NVP(a);
         ar & <EXT_BINARY_FILE>(c);
     }

    static void Save(const MyClass& inst, const std::string& filename)
    {
        std::ofstream ofs(filename);
        boost::archive::xml_oarchive oa(ofs);
        oa << BOOST_SERIALIZATION_NVP(input);
    }

    static void Load(MyClass& inst, const std::string& filename)
    {
        std::ifstream ifs(filename);
        boost::archive::xml_iarchive ia(ifs);
        ia >> BOOST_SERIALIZATION_NVP(input);
    }

    private:

        int a;
        std::vector<int> b;
};

In the files system I'd obtain the following file tree:

  • myclass.xml
  • c.dat

Do you know any way to implement this <EXT_BINARY_FILE> operation please?

Eric David
  • 261
  • 3
  • 14

1 Answers1

1

Sure. The correct way to do this would be to implement your own archive type¹. This could be involved and I have no experience with it.

So, you can probably mimick it with a serialization wrapper with some limitations:

  • I've not made de tag name configurable
  • You might want to inject a target directory to contain the generated binary archives. Right now it just assumes the current working directory is going to make sense.
  • object tracking for nested objects is out the window: because you write the wrapped objects to a new archive every time, it will always be the first time the object is seen by the tracking for that archive.

A Simple Wrapper

Here's the wrapper:

namespace uuids = boost::uuids;

template <typename T>
struct ExtBinaryFile {
    using base = boost::serialization::nvp<T>;
    ExtBinaryFile(T& ref) : ref_(ref) {}
  private:
    T& ref_;
    friend boost::serialization::access;

    template <typename Ar> void save(Ar& ar, unsigned) const {
        auto src = random_name();
        {
            std::ofstream ofs(src, std::ios::binary);
            boost::archive::binary_oarchive boa(ofs);
            boa << ref_;
        }
        ar & BOOST_SERIALIZATION_NVP(src);
    }

    template <typename Ar> void load(Ar& ar, unsigned) {
        std::string src;
        ar & BOOST_SERIALIZATION_NVP(src);
        {
            std::ifstream ifs(src, std::ios::binary);
            boost::archive::binary_iarchive bia(ifs);
            bia >> ref_;
        }
    }

    template <typename Ar> void serialize(Ar& ar, unsigned version) {
        boost::serialization::split_member(ar, *this, version);
        //if constexpr(Ar::is_saving::value) {
            //this->save(ar, version);
        //} else {
            //this->load(ar, version);
        //}
    }

    static std::string random_name() {
        std::ostringstream oss;
        std::mt19937 prng(std::random_device{}());
        uuids::basic_random_generator<std::mt19937> gen{prng};
        oss << gen() << ".dat";
        return oss.str();
    }
};

As you can see

  • I paved over the problem of generating filenames by generating random-based UUIDs.
  • The only thing that the XML will contain is a src element to contain the filename with the data.

Type Traits

As given the wrapper would not work because it violates assumptions made by the library regarding object identity. Let's tweak some serialization traits to fix that:

namespace boost { namespace serialization {
    template <typename T> struct is_wrapper<ExtBinaryFile<T> > : std::true_type {};
    template <typename T> struct tracking_level<ExtBinaryFile<T> > {
        static const tracking_type value = tracking_type::track_never;
    };
    // const versions for completeness
    template <typename T> struct is_wrapper<const ExtBinaryFile<T> > : is_wrapper<ExtBinaryFile<T> > {};
    template <typename T> struct tracking_level<const ExtBinaryFile<T> > : tracking_level<ExtBinaryFile<T> > {};
} }

There. Now we don't accidentally think the identity of the wrapper is important.

Demo

class MyClass {
  public:
    friend class boost::serialization::access;
    template <class Archive>
    void serialize(Archive& ar, unsigned) {
        auto dat = make_ext_binary(b);

        ar & BOOST_SERIALIZATION_NVP(a)
           & BOOST_SERIALIZATION_NVP(dat)
           ;
    }

    static void Save(const MyClass& inst, const std::string& filename) {
        std::ofstream ofs(filename);
        boost::archive::xml_oarchive oa(ofs);
        oa << BOOST_SERIALIZATION_NVP(inst);
    }

    static void Load(MyClass& inst, const std::string& filename) {
        std::ifstream ifs(filename);
        boost::archive::xml_iarchive ia(ifs);
        ia >> BOOST_SERIALIZATION_NVP(inst);
    }

  //private:
    int a = 0;
    std::vector<int> b;
};

int main() {
    {
        MyClass orig;
        orig.a = 99;
        std::generate_n(back_inserter(orig.b), 1024, ::rand);
        MyClass::Save(orig, "here.xml");
    }
    {
        MyClass roundtrip;
        MyClass::Load(roundtrip, "here.xml");

        std::cout << "a: " << roundtrip.a << " b:" << roundtrip.b.size() << " elements\n";
    }
}

As you can see I removed the private: for a second so we can prove that the deserialized instance roundtripped correctly.

See It Live On Coliru

g++ -std=c++17 -O2 -Wall -pedantic -pthread main.cpp -lboost_serialization
./a.out; ls -ltrah here.xml *.dat
a: 99 b:1024 elements
-rw-r--r-- 1 2001 2000  365 Jun  9 18:03 here.xml
-rw-r--r-- 1 2001 2000 4.1K Jun  9 18:03 55a89355-2637-42e4-b285-9846b046485e.dat

here.xml:

<?xml version="1.0" encoding="UTF-8" standalone="yes" ?>
<!DOCTYPE boost_serialization>
<boost_serialization signature="serialization::archive" version="18">
<inst class_id="0" tracking_level="0" version="0">
    <a>99</a>
    <dat class_id="1" tracking_level="0" version="0">
        <src>e783d3b6-0b23-4fad-be2c-97be9b1e0575.dat</src>
    </dat>
</inst>
</boost_serialization>

Full Code

Live On Coliru

#include <boost/archive/binary_oarchive.hpp>
#include <boost/archive/binary_iarchive.hpp>
#include <boost/archive/xml_oarchive.hpp>
#include <boost/archive/xml_iarchive.hpp>
#include <boost/serialization/vector.hpp>
#include <boost/serialization/serialization.hpp>
#include <fstream>
#include <random>
#include <boost/uuid/random_generator.hpp>
#include <boost/uuid/uuid_io.hpp>

namespace uuids = boost::uuids;

template <typename T>
struct ExtBinaryFile {
    using base = boost::serialization::nvp<T>;
    ExtBinaryFile(T& ref) : ref_(ref) {}
  private:
    T& ref_;
    friend boost::serialization::access;

    template <typename Ar> void save(Ar& ar, unsigned) const {
        auto src = random_name();
        {
            std::ofstream ofs(src, std::ios::binary);
            boost::archive::binary_oarchive boa(ofs);
            boa << ref_;
        }
        ar & BOOST_SERIALIZATION_NVP(src);
    }

    template <typename Ar> void load(Ar& ar, unsigned) {
        std::string src;
        ar & BOOST_SERIALIZATION_NVP(src);
        {
            std::ifstream ifs(src, std::ios::binary);
            boost::archive::binary_iarchive bia(ifs);
            bia >> ref_;
        }
    }

    template <typename Ar> void serialize(Ar& ar, unsigned version) {
        boost::serialization::split_member(ar, *this, version);
        //if constexpr(Ar::is_saving::value) {
            //this->save(ar, version);
        //} else {
            //this->load(ar, version);
        //}
    }

    static std::string random_name() {
        std::ostringstream oss;
        std::mt19937 prng(std::random_device{}());
        uuids::basic_random_generator<std::mt19937> gen{prng};
        oss << gen() << ".dat";
        return oss.str();
    }
};

namespace boost { namespace serialization {
    template <typename T> struct is_wrapper<ExtBinaryFile<T> > : std::true_type {};
    template <typename T> struct tracking_level<ExtBinaryFile<T> > {
        static const tracking_type value = tracking_type::track_never;
    };
    // const versions for completeness
    template <typename T> struct is_wrapper<const ExtBinaryFile<T> > : is_wrapper<ExtBinaryFile<T> > {};
    template <typename T> struct tracking_level<const ExtBinaryFile<T> > : tracking_level<ExtBinaryFile<T> > {};
} }

template <typename T>
static inline ExtBinaryFile<T> const make_ext_binary(T& ref) {
    return {ref};
}

class MyClass {
  public:
    friend class boost::serialization::access;
    template <class Archive>
    void serialize(Archive& ar, unsigned) {
        auto dat = make_ext_binary(b);

        ar & BOOST_SERIALIZATION_NVP(a)
           & BOOST_SERIALIZATION_NVP(dat)
           ;
    }

    static void Save(const MyClass& inst, const std::string& filename) {
        std::ofstream ofs(filename);
        boost::archive::xml_oarchive oa(ofs);
        oa << BOOST_SERIALIZATION_NVP(inst);
    }

    static void Load(MyClass& inst, const std::string& filename) {
        std::ifstream ifs(filename);
        boost::archive::xml_iarchive ia(ifs);
        ia >> BOOST_SERIALIZATION_NVP(inst);
    }

  //private:
    int a = 0;
    std::vector<int> b;
};

#include <iostream>

int main() {
    {
        MyClass orig;
        orig.a = 99;
        std::generate_n(back_inserter(orig.b), 1024, ::rand);
        MyClass::Save(orig, "here.xml");
    }
    {
        MyClass roundtrip;
        MyClass::Load(roundtrip, "here.xml");

        std::cout << "a: " << roundtrip.a << " b:" << roundtrip.b.size() << " elements\n";
    }
}

¹ Creating own implementation of Boost::Archive

sehe
  • 374,641
  • 47
  • 450
  • 633