Serializing a map<fs::path, fs::path>?

527 Views Asked by At

I'm using the cereal library to serialize my classes into files, but am running into trouble with std::map - specifically, maps that use std::filesystem::path.

Say I have an Object class that contains only a map<fs::path, fs::path>, as well as the required serialize function for cereal:

struct Object
{
    map<fs::path, fs::path> _map;

    template<class Archive>
    void serialize(Archive & archive)
    {
        archive(_map);
    }

    friend ostream& operator<<(ostream& outs, const Object c)
    {
        for (auto const &pair: c._map)
            std::cout << "{" << pair.first << ": " << pair.second << "}\n";
        return outs;
    }
};

In my main function, I have:

int main()
{
    // create Object
    cout << "Creating object..." << endl;
    Object o;
    fs::path a = "bye.txt";
    fs::path b = "hello.txt";
    o._map[a] = b;
    o._map[b] = a;
    cout << "Object created: " << endl;
    cout << o;

    // serialize
    cout << "Serializing object...." << endl;
    stringstream ss;
    cereal::BinaryOutputArchive oarchive(ss);
    oarchive(o);
    cout << "Object serialized." << endl;

    // write to file
    cout << "Writing serialized object to file...." << endl;
    ofstream file("serialized_object");
    file << ss.str();
    file.close();
    cout << "Object written to file." << endl;

    // read from file
    cout << "Reading from file..." << endl;
    stringstream ss2;
    fs::path ins = "serialized_object";
    ifstream file_stream(ins, ios::binary);
    ss2 << file_stream.rdbuf();
    cereal::BinaryInputArchive iarchive(ss2);
    Object out;
    iarchive(out);
    cout << "Object read from file." << endl;
    cout << out;
}

In my output, I see the error when it reads from the serialized file:

Creating object...
Object created:
{"bye.txt": "hello.txt"}
{"hello.txt": "bye.txt"}
Serializing object....
Object serialized.
Writing serialized object to file....
Object written to file.
Reading from file...
terminate called after throwing an instance of 'cereal::Exception'
  what():  Failed to read 2573 bytes from input stream! Read 28

My includes are:

#include <iostream>
#include <filesystem>
#include <fstream>
#include <string.h>
#include <map>
#include "cereal/types/map.hpp"
#include "cereal/archives/binary.hpp"
#include "cereal/types/string.hpp"

And I have included the following code at the beginning in order to be able to serialize fs::path:

namespace std
{
  namespace filesystem
  {
    template<class Archive>
    void CEREAL_LOAD_MINIMAL_FUNCTION_NAME(const Archive&, path& out, const string& in)
    {
        out = in;
    }

    template<class Archive>
    string CEREAL_SAVE_MINIMAL_FUNCTION_NAME(const Archive& ar, const path& p)
    {
      return p.string();
    }
  }
}

I'm sure I'm missing something obvious, as this is my first time using cereal. Does anyone have any insight as to why I'm running into this issue?

1

There are 1 best solutions below

0
On

Based on the cereal documentation, you must always use the ios::binary flag when utilizing the BinaryArchive. Here is the specific section from this page in their documentation:

When using a binary archive and a file stream (std::fstream), remember to specify the binary flag (std::ios::binary) when constructing the stream. This prevents the stream from interpreting your data as ASCII characters and altering them.

The other issue is based on how Cereal guarantees that the serialization has completed. Per this link below, the output archive object should go out of scope (invoking the destructor) prior to any attempt to read the archive. They utilize the RAII approach, just like ifstream and ofstream as noted here.

You can greatly simplify your code by allowing the Cereal library perform the writes and reads internally. Here is a modified version of your main function:

int main()
{
    // create Object
    cout << "Creating object..." << endl;
    Object o;
    fs::path a = "bye.txt";
    fs::path b = "hello.txt";
    o._map[a] = b;
    o._map[b] = a;
    cout << "Object created: " << endl;
    cout << o;

    
    cout << "Serializing object...." << endl;
    // scope boundary (a function call would be cleaner)
    {
        ofstream ofstm("serialized_object", ios::binary);
        cereal::BinaryOutputArchive oarchive(ofstm);
        // serialize
        oarchive(o);
    }
    cout << "Object serialized and written." << endl;

    // deserialize the object by reading from the archive
    Object out;

    // scope boundary (a function would be cleaner)
    {
        ifstream istm("serialized_object", ios::binary);
        cereal::BinaryInputArchive iarchive(istm);
        //deserialize
        iarchive(out);
    }
    cout << "Object read from file." << endl;
    cout << out;
}

I hope this helps. Please test this all out prior to utilization.