Boost serialization : forward compatibility fail with input stream error

放肆的年华 提交于 2020-01-21 19:29:49

问题


Following this question : Boost serialize child class I'm trying to support forward compatibility for my archive generated with boost serialization but i'm having trouble reading a newer archive with older code :

    class A {
    public:
        A() {}
        virtual ~A() = default;

    private:
        friend class boost::serialization::access;
        template <class Archive> void serialize(Archive &ar, const unsigned int version) {
            ar &mAttributeFromA;
        }

        std::string mAttributeFromA = "mAttributeFromA";
    };
    BOOST_CLASS_VERSION(A, 0)

    class B : public A {
    public:
        B() {}

    private:
        friend class boost::serialization::access;
        template <class Archive> void serialize(Archive &ar, const unsigned int version)
        {
            ar &boost::serialization::base_object<A>(*this);
            ar &mAttributeFromB;
            if (version == 1)
                ar &mNewAttribute;
        }

        std::string mAttributeFromB = "mAttributeFromB";
        std::string mNewAttribute = "mNewAttribute";
    };

    BOOST_CLASS_VERSION(B, 1)


    class Manager {
    public:
        boost::ptr_vector<A> mListOfA; // can store A or B
    private:
        friend class boost::serialization::access;

        template <class Archive> void serialize(Archive &ar, const unsigned int /*version*/) { ar &mListOfA; }
    };
    BOOST_CLASS_VERSION(Manager, 0)


    int main() {
        Manager  mgr;
        mgr.mListOfA.push_back(new B);
        mgr.mListOfA.push_back(new B);

        std::ofstream ofs("myFile.txt");
        {
            boost::archive::text_oarchive oa(ofs);
            oa << mgr;
        }

        try {
            Manager  mgr2;
            std::ifstream ifs("myFile.txt");
            boost::archive::text_iarchive ia(ifs);
            ia >> mgr2;
            mgr2.mListOfA.at(0);
        } catch(boost::archive::archive_exception e)
        {
            e.what();
        }
    }
BOOST_CLASS_EXPORT(A)
BOOST_CLASS_EXPORT(B)

this will generate the following archive :

22 serialization::archive 13 0 0 0 0 2 3 1 B 1 1
0 1 0
1 15 mAttributeFromA 15 mAttributeFromB 13 mNewAttribute 3
2
3 15 mAttributeFromA 15 mAttributeFromB 13 mNewAttribute

If i try to reload the archive with the same code , everything works perfectly.

However if i try to load the archive with an older version of the code : (Class version is 0 and mNewAttribute is gone)

class B : public A {
    public:
        B() {}

    private:
        friend class boost::serialization::access;
        template <class Archive> void serialize(Archive &ar, const unsigned int version)
        {
            ar &boost::serialization::base_object<A>(*this);
            ar &mAttributeFromB;
        }

        std::string mAttributeFromB = "mAttributeFromB";
    };

    BOOST_CLASS_VERSION(B, 0)

The deserialization throw me an "input stream error"

On Coliru

How can i deserialize new archive with old code ?

-- Edit -- Strangely, if i add A and B objects inside the manager it's working. But fail with only A or only B ...


回答1:


Your types aren't polymorphic. Versioning likely has nothing to do with things.

  • http://www.boost.org/doc/libs/1_60_0/libs/serialization/doc/serialization.html#derivedpointers

    It turns out that the kind of object serialized depends upon whether the base class (base in this case) is polymophic or not. If base is not polymorphic, that is if it has no virtual functions, then an object of the type base will be serialized. Information in any derived classes will be lost. If this is what is desired (it usually isn't) then no other effort is required.

You can verify this easily: the vector only deserializes As:

Live On Coliru

#include <boost/serialization/serialization.hpp>
#include <boost/serialization/version.hpp>

class A {
  public:
    A(){}

  private:
    friend class boost::serialization::access;
    template <class Archive> void serialize(Archive &ar, const unsigned int version) { ar &mAttributeFromA; }

    std::string mAttributeFromA = "mAttributeFromA";
};
BOOST_CLASS_VERSION(A, 0)

class B : public A {
  public:
    B(){}

  private:
    friend class boost::serialization::access;
    template <class Archive> void serialize(Archive &ar, const unsigned int version) {
        ar &mAttributeFromB;
        if (version == 1)
            ar &mNewAttribute;
    }

    std::string mAttributeFromB = "mAttributeFromB";
    std::string mNewAttribute   = "mNewAttribute";
};

BOOST_CLASS_VERSION(B, 1)

#include <boost/ptr_container/serialize_ptr_vector.hpp>

class Manager {
  public:
    boost::ptr_vector<A> mListOfA; // can store A or B
  private:
    friend class boost::serialization::access;

    template <class Archive> void serialize(Archive &ar, const unsigned int /*version*/) { ar &mListOfA; }
};
BOOST_CLASS_VERSION(Manager, 0)

#include <boost/archive/text_oarchive.hpp>
#include <boost/archive/text_iarchive.hpp>
#include <sstream>

int main() {
    using namespace boost;

    std::stringstream ss;

    { 
        archive::text_oarchive oa(ss); 
        Manager mgr;
        mgr.mListOfA.push_back(new A);
        mgr.mListOfA.push_back(new B);

        oa << mgr;
    }

    std::cout << ss.str() << "\n";

    { 
        archive::text_iarchive ia(ss); 
        Manager mgr;

        ia >> mgr;

        std::cout << "Deserialized: " << mgr.mListOfA.size() << "\n";
    }
}

Prints

22 serialization::archive 13 0 0 0 0 2 2 1 0
0 15 mAttributeFromA 2
1 15 mAttributeFromA

Deserialized: 2

Solution:

  1. Make the hierarchy actually polymorphic
  2. Add serialization of base object
  3. Register derived types
  4. ???
  5. Profit!

Sample (WIP) https://www.livecoding.tv/sehe/

Live On Coliru

#include <boost/serialization/serialization.hpp>
#include <boost/serialization/base_object.hpp>
#include <boost/serialization/export.hpp>
#include <boost/serialization/version.hpp>

class A {
  public:
    A(){}
    virtual ~A() = default;

  private:
    friend class boost::serialization::access;
    template <class Archive> void serialize(Archive &ar, const unsigned int version) {
        ar &mAttributeFromA; 
    }

    std::string mAttributeFromA = "mAttributeFromA";
};
BOOST_CLASS_VERSION(A, 0)

class B : public A {
  public:
    B(){}

  private:
    friend class boost::serialization::access;
    template <class Archive> void serialize(Archive &ar, const unsigned int version)
    {
        ar &boost::serialization::base_object<A>(*this);
        ar &mAttributeFromB;
        if (version == 1)
            ar &mNewAttribute;
    }

    std::string mAttributeFromB = "mAttributeFromB";
    std::string mNewAttribute   = "mNewAttribute";
};

BOOST_CLASS_VERSION(B, 1)
BOOST_CLASS_EXPORT(A)
BOOST_CLASS_EXPORT(B)

#include <boost/ptr_container/serialize_ptr_vector.hpp>

class Manager {
  public:
    boost::ptr_vector<A> mListOfA; // can store A or B
  private:
    friend class boost::serialization::access;

    template <class Archive> void serialize(Archive &ar, const unsigned int /*version*/) { ar &mListOfA; }
};
BOOST_CLASS_VERSION(Manager, 0)

#include <boost/archive/text_oarchive.hpp>
#include <boost/archive/text_iarchive.hpp>
#include <sstream>

int main() {
    using namespace boost;

    std::stringstream ss;

    { 
        archive::text_oarchive oa(ss); 
        Manager mgr;
        mgr.mListOfA.push_back(new A);
        mgr.mListOfA.push_back(new B);

        oa << mgr;
    }

    std::cout << ss.str() << "\n";

    { 
        archive::text_iarchive ia(ss); 
        Manager mgr;

        ia >> mgr;

        std::cout << "Deserialized: " << mgr.mListOfA.size() << "\n";
    }
}

Prints

22 serialization::archive 13 0 0 0 0 2 2 1 0
0 15 mAttributeFromA 3 1 B 1 1
1
2 15 mAttributeFromA 15 mAttributeFromB 13 mNewAttribute

Deserialized: 2



回答2:


Standard boost archives (including binary) do not support forward (upward) compatibility.

There is a patch for xml archive (proposed 5 years ago, still not included), which allows partial forward compatibility - skipping unknown fields.

There is an experimental 3rdaprty ptree archive, which also supports adding new fields.

Enabling forward compatibility for binary archive will require significant modifications in it.

Google protocol buffers offer forward compatibility out of the box.



来源:https://stackoverflow.com/questions/35055936/boost-serialization-forward-compatibility-fail-with-input-stream-error

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!