dodol
dodol

Reputation: 1091

boost serialization of stl collection of std unique_ptrs

I'd like to be able to serialize stl container of std::unique_ptrs. Can it be done? btw, everything works fine with single std::unique_ptr. Below is the code I'm working on, and the gcc gives the folowing error:

 use of deleted function ‘std::unique_ptr<_Tp, _Dp>::unique_ptr(const
 std::unique_ptr<_Tp, _Dp>&) [with _Tp = MyDegrees; _Dp =
 std::default_delete<MyDegrees>; std::unique_ptr<_Tp, _Dp> =
 std::unique_ptr<MyDegrees>]’

How can I make the code to work?

#include <iostream>
#include <memory>
#include <fstream>
#include <map>
#include <vector>
#include <boost/serialization/map.hpp>
#include <boost/serialization/vector.hpp>
#include <boost/archive/text_oarchive.hpp>
#include <boost/archive/text_iarchive.hpp>
#include <boost/archive/xml_oarchive.hpp>
#include <boost/archive/xml_iarchive.hpp>
namespace boost {
namespace serialization {

template<class Archive, class T>
inline void save(
    Archive & ar,
    const std::unique_ptr< T > &t,
    const unsigned int file_version
) {
    // only the raw pointer has to be saved
    const T * const tx = t.get();
    //ar << tx;
    ar << boost::serialization::make_nvp("px", tx);
}
template<class Archive, class T>
inline void load(
    Archive & ar,
    std::unique_ptr< T > &t,
    const unsigned int file_version
) {
    T *pTarget;
    //ar >> pTarget;
    ar >> boost::serialization::make_nvp("px", pTarget);

#if BOOST_WORKAROUND(BOOST_DINKUMWARE_STDLIB, == 1)
    t.release();
    t = std::unique_ptr< T >(pTarget);
#else
    t.reset(pTarget);
#endif
}
template<class Archive, class T>
inline void serialize(
    Archive & ar,
    std::unique_ptr< T > &t,
    const unsigned int file_version
) {
    boost::serialization::split_free(ar, t, file_version);
}
} // namespace serialization
} // namespace boost

class MyDegrees
{
public:
    void setDeg(int d) {
        deg = d;
    }
    int getDeg()const {
        return deg;
    }
private:
    friend class boost::serialization::access;
    template<class Archive>
    void serialize(Archive & ar, const unsigned int version)
    //{ ar & deg; }
    {
        ar & boost::serialization::make_nvp("DEGS", deg);
    }
    int deg;
};
class gps_position
{
private:
    friend class boost::serialization::access;
    template<class Archive>
    void serialize(Archive & ar, const unsigned int version)
    //{ ar & degrees;  }
    {
        ar & boost::serialization::make_nvp("DEGS2", degrees);
        ar & boost::serialization::make_nvp("DEGMAP", deg_map);
    }
    std::unique_ptr<MyDegrees> degrees;
    std::vector<std::unique_ptr<MyDegrees> > deg_map;
public:
    gps_position(): degrees(std::unique_ptr<MyDegrees>(new MyDegrees)) {};
    void setDeg(int d) {
        degrees->setDeg(d);
    }
    int getDeg() const {
        return degrees->getDeg();
    }
};

int TestBasicSerialize(int, char *[])
{
    int numErr = 0;
    double a;
    std::ofstream ofs("filename");
    gps_position g;
    g.setDeg(45);
    std::cout<<g.getDeg()<<std::endl;
    {
        boost::archive::text_oarchive oa(ofs);
        oa << g;
    }
    //{ boost ::archive::xml_oarchive oa(ofs); oa << g;}
    gps_position newg;
    {
        std::ifstream ifs("filename");
        boost::archive::text_iarchive ia(ifs);
        ia >> newg;
        std::cout<<newg.getDeg()<<std::endl;
    }
    return numErr;

}

Upvotes: 4

Views: 1905

Answers (1)

Anonymous Coward
Anonymous Coward

Reputation: 6226

The problem is that the container deserializer is trying to copy-construct a unique_ptr. To demonstrate, consider the following code which yields the same error:

std::vector< std::unique_ptr<int> > vec;
std::unique_ptr<int> p;
vec.push_back(p); // does not compile!

However, this can be solved using std::move:

vec.push_back(std::move(p)); // ok

The least-effort solution would be using a copy constructible smart pointer instead, for example boost::shared_ptr, which comes with its predefined serialization implementation in boost/serialization/shared_ptr.hpp.


The next solution is to serialize the vector manually inside your class:

//NOTE: this replaces void serialize(...) in gps_position
template<class Archive>
void save(Archive & ar, const unsigned int version) const
{
    ar & BOOST_SERIALIZATION_NVP(degrees);
    size_t size = deg_map.size();
    ar & BOOST_SERIALIZATION_NVP(size);
    for( auto it=deg_map.begin(), end=deg_map.end(); it!=end; ++it )
       ar & boost::serialization::make_nvp("item",*it);
}
template<class Archive>
void load(Archive & ar, const unsigned int version)
{
    ar & BOOST_SERIALIZATION_NVP(degrees);
    size_t size = 0;
    ar & BOOST_SERIALIZATION_NVP(size);
    deg_map.clear();
    deg_map.reserve(size);
    while( size-- >= 0 ) {
        std::unique_ptr<MyDegrees> p;
        ar & boost::serialization::make_nvp("item",p);
        deg_map.push_back(std::move(p));
    }
}
BOOST_SERIALIZATION_SPLIT_MEMBER()

The last solution involves writing your own container serializer, like you did for unique_ptr, that uses std::move to add items:

namespace boost { namespace serialization {
//NOTE: do not include boost/serialization/vector.hpp
template<class Archive, class T, class Allocator>
inline void save(
    Archive & ar,
    const std::vector<T, Allocator> &t,
    const unsigned int
){
    collection_size_type count (t.size());
    ar << BOOST_SERIALIZATION_NVP(count);
    for(auto it=t.begin(), end=t.end(); it!=end; ++it)
        ar << boost::serialization::make_nvp("item", (*it));
}

template<class Archive, class T, class Allocator>
inline void load(
    Archive & ar,
    std::vector<T, Allocator> &t,
    const unsigned int
){
    collection_size_type count;
    ar >> BOOST_SERIALIZATION_NVP(count);
    t.clear();
    t.reserve(count);
    while( count-- > 0 ) {
        T i;
        ar >> boost::serialization::make_nvp("item", i);
        t.push_back(std::move(i)); // use std::move
    }
}

template<class Archive, class T, class Allocator>
inline void serialize(
    Archive & ar,
    std::vector<T, Allocator> & t,
    const unsigned int file_version
){
    boost::serialization::split_free(ar, t, file_version);
}
} } // namespace boost::serialization

And last but not least, you should use:

oa << BOOST_SERIALIZATION_NVP(g);
// and
ia >> BOOST_SERIALIZATION_NVP(newg);

in your TestBasicSerialize() when using the xml archive.

Upvotes: 6

Related Questions