带有私有默认构造函数的Boost::serialization: object适用于vector,但不适用于map

boost::serialization: object with private default constructor works in a vector, but not in a map

本文关键字:适用于 vector map 不适用 object serialization 默认 构造函数 Boost      更新时间:2023-10-16

考虑以下代码:

#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);
}

在序列化foo向量时可以正常工作。但是,如果我尝试序列化foo的映射,它会在私有默认构造函数上失败:

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);

失败

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){}
    ^

我正在使用clangUbuntu clang version 3.4-1ubuntu3 (tags/RELEASE_34/final)(基于LLVM 3.4)

和Ubuntu 14.04LTS附带的boost版本1.55。

我已经尝试提供load_construct_data()函数如下:

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

,但我仍然得到相同的错误,因为它需要构造函数时实例化std::pair

哦。啊哈。

我只是使用Boost 1.57.0来比较map<string, Foo>的情况。

好吧,你很幸运。您发现了另一个库版本依赖项(可能是错误)。

  • 不使用这个,但是提供了私有的默认构造函数,GCC 4.8.2可以很好地编译它:Live On Coliru[1]

  • GCC 4.9.0无法编译它(它也使用了一个新版本的标准库)。std::pair<>的默认构造函数无法在那里编译,因为Foo不是默认构造函数:Live On Coliru

解决方案

幸运的是,save_construct_data/load_construct_data的解决方案再次拯救了一天。

但是,您需要考虑元素类型实际上不是Foo,而是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);
}

现在一切正常:

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";
    }
}

打印:

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](我不能在Coliru上链接它,因为那里的boost库已经被重新编译为GCC 5.0 ABI)

指出

一个更好的、通用的解决方案是对boost::serialization命名空间中的不可默认构造类型通用地使用load/save_construct_data技巧。这样,人们就不必"知道"std::pair<>的实现细节。他们可以为自己的用户类型实现load/save_construct_data,无论他们把它放在vector或map中,它都可以JustWork™。

通用地实现它并不是微不足道的,而且可能会干扰Boost序列化框架内部的一些其他机制。

我更愿意得到Boost序列化维护者的帮助,以一种可靠的方式做到这一点。那么,看来我今天要提交两张票了。

对于vector也不一定有效。向量反序列化首先将向量大小调整为所需的大小。这要求元素必须是默认可构造的。

注意,这只是一个问题,因为

  1. 构造不能通过serialization::access好友"令牌"访问
  2. 类是默认可构造的

解决方案文档

文档告诉你使用save_construct_dataload_construct_data的类型是不可默认构造的。

他们特别承诺它也将适用于这些STL容器:

除了指针的反序列化之外,这些重写还用于元素类型没有默认构造函数的STL容器的反序列化。

实际上,这在v1.57.0中工作得很好:

  • Live On Coliru

但在1.58.0,这不是真的…

Boost 1.58.0的Bug

1.58.0版本似乎打破了这个规则:

代码似乎做了必要的检查(从load(...)的未优化版本的serialization/vector.hpp):

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

但是,这会在运行时进行检查。该方法将静态地拒绝编译。哎呀。

修复

与其在相同的流代码中拥有分支,不如分派它,以便只实例化适用的分支。我用这种简单的方法进行了测试:

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>());
}

它起作用了。

总结遗憾的是,我现在没有时间调查map<>的情况。但我怀疑事情是相似的。文档化的解决方案应该仍然有效。而且它可能还是坏的。

今天晚些时候我会向boost的问题跟踪器报告上述问题。

我希望我的答案能帮助你找到解决问题的方法。