Boost :: serialization: object with private constructor by default works in vector, but not in map

Consider the following code:

#include <boost/serialization/nvp.hpp>
#include <boost/archive/xml_iarchive.hpp>
#include <boost/archive/xml_oarchive.hpp>

class Foo{
    friend class boost::serialization::access;
    template<class Archive>
    void serialize(Archive & ar, const unsigned int)
    {
        ar & BOOST_SERIALIZATION_NVP(i);
    }
    int i;
    Foo():i(0){}
public:
    Foo(int k):i(k){}
};

int main(int argc, char *argv[])
{
    std::vector< Foo> f;
    f.push_back(Foo(12));
    std::ofstream os("path");
    boost::archive::xml_oarchive oa(os);
    oa << boost::serialization::make_nvp("f", f);
    os.close();
    std::vector<Foo> g;
    std::ifstream is("path");
    boost::archive::xml_iarchive ia(is);
    ia >> boost::serialization::make_nvp("f", g);
}

      

Which works great when serializing the Foos vector. However, if I try to serialize the Foos map, it fails in its own default constructor:

std::map<std::string, Foo> f;
f.insert(std::make_pair("hello", Foo(12)));
std::ofstream os("path");
boost::archive::xml_oarchive oa(os);
oa << boost::serialization::make_nvp("f", f);
os.close();
std::map<std::string, Foo> g;
std::ifstream is("path");
boost::archive::xml_iarchive ia(is);
ia >> boost::serialization::make_nvp("f", g);

      

doesn't work with

In file included from main.cpp:2:
In file included from /usr/local/include/boost/serialization/nvp.hpp:19:
In file included from /usr/bin/../lib/gcc/x86_64-linux-gnu/4.8/../../../../include/c++/4.8/utility:70:
/usr/bin/../lib/gcc/x86_64-linux-gnu/4.8/../../../../include/c++/4.8/bits/stl_pair.h:109:18: error: field of type 'Foo' has private default constructor
      : first(), second() { }
                 ^
/usr/local/include/boost/serialization/access.hpp:132:17: note: in instantiation of member function 'std::pair<const std::basic_string<char>, Foo>::pair' requested here
        ::new(t)T;
                ^
/usr/local/include/boost/serialization/serialization.hpp:93:13: note: in instantiation of function template specialization 'boost::serialization::access::construct<std::pair<const std::basic_string<char>, Foo> >' requested here
    access::construct(t);
            ^
/usr/local/include/boost/serialization/serialization.hpp:158:9: note: in instantiation of function template specialization 'boost::serialization::load_construct_data<boost::archive::xml_iarchive, std::pair<const std::basic_string<char>, Foo> >' requested here
        load_construct_data(ar, t, v);
        ^
/usr/local/include/boost/serialization/detail/stack_constructor.hpp:58:31: note: in instantiation of function template specialization 'boost::serialization::load_construct_data_adl<boost::archive::xml_iarchive, std::pair<const std::basic_string<char>, Foo> >' requested here
        boost::serialization::load_construct_data_adl(
                              ^
/usr/local/include/boost/serialization/collections_load_imp.hpp:83:48: note: in instantiation of member function 'boost::serialization::detail::stack_construct<boost::archive::xml_iarchive, std::pair<const std::basic_string<char>, Foo> >::stack_construct' requested here
        detail::stack_construct<Archive, type> t(ar, v);
                                               ^
/usr/local/include/boost/serialization/collections_load_imp.hpp:158:16: note: (skipping 12 contexts in backtrace; use -ftemplate-backtrace-limit=0 to see all)
        hint = ifunc(ar, s, item_version, hint);
               ^
/usr/local/include/boost/archive/detail/common_iarchive.hpp:66:18: note: in instantiation of function template specialization 'boost::archive::load<boost::archive::xml_iarchive, std::map<std::basic_string<char>, Foo, std::less<std::basic_string<char> >, std::allocator<std::pair<const std::basic_string<char>, Foo> > > >' requested here
        archive::load(* this->This(), t);
                 ^
/usr/local/include/boost/archive/basic_xml_iarchive.hpp:86:39: note: in instantiation of function template specialization 'boost::archive::detail::common_iarchive<boost::archive::xml_iarchive>::load_override<std::map<std::basic_string<char>, Foo, std::less<std::basic_string<char> >, std::allocator<std::pair<const std::basic_string<char>, Foo> > > >' requested here
        this->detail_common_iarchive::load_override(t.value(), 0);
                                      ^
/usr/local/include/boost/archive/xml_iarchive.hpp:93:38: note: in instantiation of function template specialization 'boost::archive::basic_xml_iarchive<boost::archive::xml_iarchive>::load_override<std::map<std::basic_string<char>, Foo, std::less<std::basic_string<char> >, std::allocator<std::pair<const std::basic_string<char>, Foo> > > >' requested here
        basic_xml_iarchive<Archive>::load_override(t, 0);
                                     ^
/usr/local/include/boost/archive/detail/interface_iarchive.hpp:60:23: note: in instantiation of function template specialization 'boost::archive::xml_iarchive_impl<boost::archive::xml_iarchive>::load_override<const boost::serialization::nvp<std::map<std::basic_string<char>, Foo, std::less<std::basic_string<char> >, std::allocator<std::pair<const std::basic_string<char>, Foo> > > > >' requested here
        this->This()->load_override(t, 0);
                      ^
main.cpp:50:8: note: in instantiation of function template specialization 'boost::archive::detail::interface_iarchive<boost::archive::xml_iarchive>::operator>><const boost::serialization::nvp<std::map<std::basic_string<char>, Foo, std::less<std::basic_string<char> >, std::allocator<std::pair<const std::basic_string<char>, Foo> > > > >' requested here
    ia >> boost::serialization::make_nvp("f", g);
       ^
main.cpp:34:5: note: implicitly declared private here
    Foo():i(0){}
    ^

      

I am using clang Ubuntu clang version 3.4-1ubuntu3 (tags / RELEASE_34 / final) (based on LLVM 3.4)

and upgrade version 1.55 that ships with Ubuntu 14.04LTS.

I tried to provide the load_construct_data () function like this:

namespace boost
{
    namespace serialization
    {
        template<class Archive>
        inline void load_construct_data(Archive &archive, Foo*a, unsigned int
file_version)
        {
            ::new(a)Foo(0);
        }
    }
}

      

but I still get the same error because it requires a constructor when instantiating std :: pair

+3


source to share


2 answers


O. Aha.

I just used Boost 1.57.0 to compare the situation with map<string, Foo>

.

Well, you're in luck. You found another dependency on the version of the library (probably a bug).

  • Without using this, but providing its own default constructor, GCC 4.8.2 compiles it just fine: Live On Coliru [1]

  • GCC 4.9.0 cannot compile it (it also uses a newer version of the standard library). The std::pair<>

    default constructor cannot compile because Foo

    it is not the default constructor: Live On Coliru

Decision

Fortunately, the c save_construct_data

/ solution load_construct_data

saves the day again.

However, you need to consider the fact that the element type is actually not Foo

, but std::pair<T const, Foo>

.

template <class Archive, typename K> inline friend void save_construct_data(Archive& ar, std::pair<K, Foo> const* v, const unsigned int) {
    std::cerr << __PRETTY_FUNCTION__ << "\n";
    ar & boost::serialization::make_nvp("first", v->first);
    ar & boost::serialization::make_nvp("second", v->second.i);
}
template <class Archive, typename K> inline friend void load_construct_data(Archive& ar, std::pair<K, Foo>* v, const unsigned int) {
    std::cerr << __PRETTY_FUNCTION__ << "\n";
    typename std::remove_cv<K>::type first;
    ar & boost::serialization::make_nvp("first", first);
    int tmp;
    ar & boost::serialization::make_nvp("second", tmp);
    new(v) std::pair<K, Foo>(first, tmp);
}

      

Everything works now:

Live On Coliru



#include <boost/serialization/nvp.hpp>
#include <boost/serialization/serialization.hpp>
#include <boost/archive/xml_iarchive.hpp>
#include <boost/archive/xml_oarchive.hpp>
#include <boost/serialization/access.hpp>
#include <boost/serialization/vector.hpp>
#include <boost/serialization/map.hpp>
#include <boost/version.hpp>
#include <fstream>
#include <iostream>

class Foo {
    friend class boost::serialization::access;

    template <class Archive> void serialize(Archive &, const unsigned int) {
        std::cerr << __PRETTY_FUNCTION__ << "\n";
    }

    template <class Archive, typename K> inline friend void save_construct_data(Archive& ar, std::pair<K, Foo> const* v, const unsigned int) {
        std::cerr << __PRETTY_FUNCTION__ << "\n";
        ar & boost::serialization::make_nvp("first", v->first);
        ar & boost::serialization::make_nvp("second", v->second.i);
    }
    template <class Archive, typename K> inline friend void load_construct_data(Archive& ar, std::pair<K, Foo>* v, const unsigned int) {
        std::cerr << __PRETTY_FUNCTION__ << "\n";
        typename std::remove_cv<K>::type first;
        ar & boost::serialization::make_nvp("first", first);
        int tmp;
        ar & boost::serialization::make_nvp("second", tmp);
        new(v) std::pair<K, Foo>(first, tmp);
    }

    int i;

  public:
    Foo(int k) : i(k) {}

    friend std::ostream& operator<<(std::ostream& os, Foo const& foo) {
        return os << "Foo { " << foo.i << " }";
    }
};

namespace boost { namespace serialization {


} }

int main() {
    using Data = std::map<std::string, Foo>;
    std::cout << "Boost version: " << BOOST_VERSION << "\n";

    {
        auto f = Data { {"a", 12 }, {"b", 42} };
        //for (auto& e : f) std::cout << e.first << ", " << e.second << "\n";
        std::ofstream os("path");
        boost::archive::xml_oarchive oa(os);
        oa << boost::serialization::make_nvp("f", f);
    }

    {
        Data g;
        std::ifstream is("path");
        boost::archive::xml_iarchive ia(is);
        ia >> boost::serialization::make_nvp("f", g);

        for (auto& e : g)
            std::cout << e.first << ", " << e.second << "\n";
    }
}

      

What prints:

Boost version: 105700
void save_construct_data(Archive&, const std::pair<K, Foo>*, unsigned int) [with Archive = boost::archive::xml_oarchive; K = const std::__cxx11::basic_string<char>]
void Foo::serialize(Archive&, unsigned int) [with Archive = boost::archive::xml_oarchive]
void save_construct_data(Archive&, const std::pair<K, Foo>*, unsigned int) [with Archive = boost::archive::xml_oarchive; K = const std::__cxx11::basic_string<char>]
void Foo::serialize(Archive&, unsigned int) [with Archive = boost::archive::xml_oarchive]
void load_construct_data(Archive&, std::pair<K, Foo>*, unsigned int) [with Archive = boost::archive::xml_iarchive; K = const std::__cxx11::basic_string<char>]
void Foo::serialize(Archive&, unsigned int) [with Archive = boost::archive::xml_iarchive]
void load_construct_data(Archive&, std::pair<K, Foo>*, unsigned int) [with Archive = boost::archive::xml_iarchive; K = const std::__cxx11::basic_string<char>]
void Foo::serialize(Archive&, unsigned int) [with Archive = boost::archive::xml_iarchive]
a, Foo { 12 }
b, Foo { 42 }

      


[1] (I cannot link it to Coliru because the boost libraries were recompiled in the GCC 5.0 ABI)

Notes

A better general solution would be to do the load/save_construct_data

general trick for non-default types in the namespace boost::serialization

. This way people don't need to "know" about the implementation details std::pair<>

. They could just implement load/save_construct_data

for their custom types, and it's JustWork ™ whether they put it in a vector or a map.

The implementation is generally less than trivial, but may interfere with some other mechanisms internal to the performance framework.

I'd rather get help from the Serialization helpers to do this in a reliable way. So, it looks like I'll post two tickets today.

+4


source


This is not necessary for a vector. Vector deserialization first resizes the vector to the required size. For this, the elements must be constructive by default.

Please note that this is only a problem because

  • Design
  • not available via serialization::access

    friend token "
  • the default class is constructive

Documented solution

The documentation tells you to use save_construct_data

andload_construct_data

for types that are not constructive by default.

In particular, they promise that they will work for STL containers too:

In addition to deserializing pointers, these overrides are used when deserializing STL containers that do not have a default constructor.

In practice, this works well in version 1.57.0:

But in 1.58.0 this is not true ...

Bug 1.58.0



Version 1.58.0 seems to have broken this:

The code seems to require validation (from serialization/vector.hpp

the unoptimized version load(...)

):

if(detail::is_default_constructible<U>()){
    t.resize(count);
    // ... snip ... 
}
else{
    t.reserve(count);
    // ... snip ... 
}

      

However, this is a runtime check. The method will statically fail to compile. Unfortunately.

Correction

Instead of having branches in the same stream code, it should be pushed in such a way that only the corresponding branch is created. I tested this simplified approach:

namespace sehe_bugfix {
    template<class Archive, class U, class Allocator>
    inline void load_elements(
        Archive & ar,
        std::vector<U, Allocator> &t,
        const unsigned int /* file_version */,
        collection_size_type count,
        mpl::true_
    ){
        const boost::archive::library_version_type library_version(
            ar.get_library_version()
        );
        item_version_type item_version(0);
        if(boost::archive::library_version_type(3) < library_version){
            ar >> BOOST_SERIALIZATION_NVP(item_version);
        }
        t.resize(count);
        typename std::vector<U, Allocator>::iterator hint;
        hint = t.begin();
        while(count-- > 0){
            ar >> boost::serialization::make_nvp("item", *hint++);
        }
    }

    template<class Archive, class U, class Allocator>
    inline void load_elements(
        Archive & ar,
        std::vector<U, Allocator> &t,
        const unsigned int /* file_version */,
        collection_size_type count,
        mpl::false_
    ){
        const boost::archive::library_version_type library_version(
            ar.get_library_version()
        );
        item_version_type item_version(0);
        if(boost::archive::library_version_type(3) < library_version){
            ar >> BOOST_SERIALIZATION_NVP(item_version);
        }
        t.reserve(count);
        while(count-- > 0){
            detail::stack_construct<Archive, U> u(ar, item_version);
            ar >> boost::serialization::make_nvp("item", u.reference());
            t.push_back(u.reference());
            ar.reset_object_address(& t.back() , & u.reference());
        }
    }
}

template<class Archive, class U, class Allocator>
inline void load(
    Archive & ar,
    std::vector<U, Allocator> &t,
    const unsigned int file_version,
    mpl::false_
){
    const boost::archive::library_version_type library_version(
        ar.get_library_version()
    );
    // retrieve number of elements
    item_version_type item_version(0);
    collection_size_type count;
    ar >> BOOST_SERIALIZATION_NVP(count);

    sehe_bugfix::load_elements(ar, t, file_version, count, detail::is_default_constructible<U>());
}

      

And it works.

ESSENCE

Unfortunately, I have no time to investigate the situation map<>

. But I suspect everything is similar. The documented solution should still work. And it can still be broken.

Today I will cover the aforementioned boost issue tracking issue.

I hope the answer helps you find a solution (s)

+3


source







All Articles