Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Boost serialization with pointers and non-default constructor

How would you serialize/deserialize this class using boost::serialization?

#include <vector>

struct Foo {
    struct Bar {
        std::vector<int> * data; // Must point to Foo::data

        Bar( std::vector<int> * d ) : data(d) { }
    };

    std::vector<int> data;
    std::vector<Bar> elements;

    Foo() {
        // do very time consuming calculation to populate "data" and "elements"
    }
};

The constructor in Foo must not be executed when the objected is loaded from the serialized data, but if the object is default constructed the constructor must be evaluated.

It is okay to add a default constructor to Bar, but after serialization the Foo::Bar::data must point to the Foo::data.

EDIT: Following is a non-working implementation of my attempt

This is my attempt based on the hints from @Matthieu. The problem is that when I deserialize Foo, I get no elements in Foo::data and Foo::elements.

struct Foo {
    struct Bar {
        std::vector<int> * data;

        Bar( ) : data( 0 ) { }
        Bar( std::vector<int> * d ) : data(d) { }

        template<class Archive>
        void serialize(Archive & ar, const unsigned int version) {
            ar & data;
        }
    };

    std::vector<int> data;
    std::vector<Bar> elements;

    Foo() {
        std::cerr << "Running default constructor" << std::endl;
        data.push_back(1);
        data.push_back(2);
        data.push_back(3);
        data.push_back(4);
        data.push_back(5);
        elements.push_back( Bar( &data ) );
        elements.push_back( Bar( &data ) );
        elements.push_back( Bar( &data ) );
    }

    template<class Archive>
    Foo( Archive & ar ) {
        ar >> data; // is this corrent?
        ar >> elements;
    }

private:
    BOOST_SERIALIZATION_SPLIT_MEMBER();
    friend class boost::serialization::access;

    template<class Archive>
    void save(Archive & ar, const unsigned int version) const {
        const std::vector<int> * data_ptr = &data;

        // should data be seriliazed as pointer...
        // it is used as a pointer in Bar
        ar << data_ptr;
        ar << elements;
    }
};

int main(int argc, const char *argv[])
{
#if 0
    // serialize
    Foo foo;
    boost::archive::text_oarchive oar(std::cout);
    oar << foo;

#else
    // deserialize
    boost::archive::text_iarchive oar(std::cin);
    Foo foo(oar);

#endif
    std::cerr << foo.data.size() << std::endl;
    std::cerr << foo.elements.size() << std::endl;

    std::cerr << (&foo.data) << std::endl;
    for( const auto& a : foo.data )
        std::cerr << a << " ";
    std::cerr << std::endl;

    for( const auto& a : foo.elements)
        std::cerr << a.data << " ";
    std::cerr << std::endl;

    return 0;
}
like image 918
Allan Avatar asked Mar 06 '12 13:03

Allan


People also ask

Is the pointer to an object serialized in boost?

The pointer – not *a – is then serialized. Boost.Serialization automatically serializes the object referenced by a and not the address of the object. If the archive is restored, a will not necessarily contain the same address.

Why can’t we serialize pointers?

Because a pointer stores the address of an object, serializing the address does not make much sense. When serializing pointers and references, the referenced object is serialized. Example 64.8. Serializing pointers Example 64.8 creates a new object of type animal with new and assigns it to the pointer a. The pointer – not *a – is then serialized.

How does Boost_serialization_split_member () work?

The macro BOOST_SERIALIZATION_SPLIT_MEMBER() generates code which invokes the save or load depending on whether the archive is used for saving or loading. Archives Our discussion here has focused on adding serialization capability to classes. The actual rendering of the data to be serialized is implemented in the archive class.

How to save and load a class via serialization?

For each class to be saved via serialization, there must exist a function to save all the class members which define the state of the class. For each class to be loaded via serialization, there must exist a function to load theese class members in the same sequence as they were saved.


1 Answers

There is a section in the documentation that describes how to (de)serialize classes with non-default constructors. See here.

Basically, you must implement two functions called save_construct_data and load_construct_data in the namespace boost::serialization to write out and read in the data used to construct instances of your class. You can then call a non-default constructor of Foo from the load_construct_data function with the parameters necessary to reconstruct a Foo object.


Here is a working example based on your updated code:

Note that I've used shared_ptr's to clarify that the data member serialized by Foo and Bar are referencing the same thing.

#include <vector>
#include <boost/archive/text_oarchive.hpp>
#include <boost/archive/text_iarchive.hpp>
#include <boost/serialization/vector.hpp>
#include <boost/serialization/shared_ptr.hpp>
#include <boost/serialization/scoped_ptr.hpp>
#include <boost/shared_ptr.hpp>
#include <iostream>
#include <sstream>

struct Foo {
    struct Bar {
        boost::shared_ptr< std::vector<int> > data; // Must point to Foo::data

        Bar( boost::shared_ptr< std::vector<int> > d ) : data(d) { }

        template<class Archive>
        void serialize(Archive & ar, const unsigned int version)
        {
          // ** note that this is empty **
        }
    };

    boost::shared_ptr< std::vector<int> > data;
    std::vector<Bar> elements;

    Foo() : data( new std::vector<int>() ) {
        std::cerr << "Running default constructor" << std::endl;
        data->push_back(1);
        data->push_back(2);
        data->push_back(3);
        data->push_back(4);
        data->push_back(5);
        elements.push_back( Bar( data ) );
        elements.push_back( Bar( data ) );
        elements.push_back( Bar( data ) );
    }

    template<class Archive>
    void serialize(Archive & ar, const unsigned int version)
    {
      // ** note that this is empty **
    }

    Foo(
      boost::shared_ptr< std::vector<int> > const & data_,
      std::vector<Bar> const & elements_ ) : data( data_ ), elements( elements_ )
    {
        std::cout << "cheap construction" << std::endl;
    }
};

namespace boost { namespace serialization {

template<class Archive>
inline void save_construct_data(
    Archive & ar, const Foo * foo, const unsigned int file_version
){
    ar << foo->data << foo->elements;
}

template<class Archive>
inline void load_construct_data(
    Archive & ar, Foo * foo, const unsigned int file_version
){
    boost::shared_ptr< std::vector<int> > data;
    std::vector<Foo::Bar> elements;

    ar >> data >> elements;

    ::new(foo)Foo(data, elements);
}

template<class Archive>
inline void save_construct_data(
    Archive & ar, const Foo::Bar * bar, const unsigned int file_version
){
    ar << bar->data;
}

template<class Archive>
inline void load_construct_data(
    Archive & ar, Foo::Bar * bar, const unsigned int file_version
){
    boost::shared_ptr< std::vector<int> > data;

    ar >> data;

    ::new(bar)Foo::Bar(data);
}

}}

int main()
{
  std::stringstream ss;

  {
    boost::scoped_ptr< Foo > foo( new Foo() );

    std::cout << "size before serialization is: " << foo->data->size() << std::endl;

    boost::archive::text_oarchive oa(ss);
    oa << foo;
  }

  {
    boost::scoped_ptr< Foo > foo;

    boost::archive::text_iarchive is(ss);
    is >> foo;

    std::cout << "size after deserialization is: " << foo->data->size() << std::endl;
  }

  return 0;
}
like image 172
Andrew Durward Avatar answered Oct 14 '22 16:10

Andrew Durward