Common confusions with serializing polymorphic types

后端 未结 2 1013
南旧
南旧 2021-01-20 08:02

I have seen many questions, tutorials, and documentation involving serializing derived classes, and I haven\'t been able to reach a consensus on several issues, including (a

相关标签:
2条回答
  • 2021-01-20 08:16

    Following @sehe's advice, here are some example uses:

    Serialize derived class object, not forwarding to parent

    #include <boost/archive/text_oarchive.hpp>
    #include <boost/archive/text_iarchive.hpp>
    
    #include <fstream>
    
    class AbstractPoint
    {
    public:
        virtual ~AbstractPoint(){}
        virtual void DoSomething() = 0;
    };
    
    class Point : public AbstractPoint
    {
    public:
        Point() = default;
        Point(const double data) : mData(data) {}
    
        void DoSomething(){}
    
        template<class TArchive>
        void serialize(TArchive& archive, const unsigned int version)
        {
            archive & mData;
        }
    
        double mData;
    };
    
    int main()
    {
        Point point(7.4);
    
        std::ofstream outputStream("test.txt");
        boost::archive::text_oarchive outputArchive(outputStream);
        outputArchive << point;
        outputStream.close();
    
        Point pointRead;
        std::ifstream inputStream("test.txt");
        boost::archive::text_iarchive inputArchive(inputStream);
        inputArchive >> pointRead;
    
        std::cout << pointRead.mData << std::endl;
        return 0;
    }
    

    Serialize derived class object, including (automatic) forwarding to parent:

    #include <boost/archive/text_oarchive.hpp>
    #include <boost/archive/text_iarchive.hpp>
    
    #include <fstream>
    
    class AbstractPoint
    {
    public:
        virtual ~AbstractPoint(){}
        virtual void DoSomething() = 0;
    
        double mParentData = 3.1;
    
        template<class TArchive>
        void serialize(TArchive& archive, const unsigned int version)
        {
            archive & mParentData;
        }
    };
    
    class Point : public AbstractPoint
    {
    public:
        Point() = default;
        Point(const double data) : mData(data) {}
    
        void DoSomething(){}
    
        template<class TArchive>
        void serialize(TArchive& archive, const unsigned int version)
        {
            // this is not required, the parent serialize() seems to be called automatically
            // archive & boost::serialization::base_object<AbstractPoint>(*this);
    
            archive & mData;
        }
    
        double mData;
    };
    
    int main()
    {
        Point point(7.4);
    
        std::ofstream outputStream("test.txt");
        boost::archive::text_oarchive outputArchive(outputStream);
        outputArchive << point;
        outputStream.close();
    
        Point pointRead;
        std::ifstream inputStream("test.txt");
        boost::archive::text_iarchive inputArchive(inputStream);
        inputArchive >> pointRead;
    
        std::cout << pointRead.mParentData << std::endl;
        std::cout << pointRead.mData << std::endl;
        return 0;
    }
    

    Serialize derived class pointer, not forwarding to parent (note nothing changes from the object case)

    #include <boost/archive/text_oarchive.hpp>
    #include <boost/archive/text_iarchive.hpp>
    #include <boost/serialization/shared_ptr.hpp>
    
    #include <fstream>
    
    class AbstractPoint
    {
    public:
        virtual ~AbstractPoint(){}
        virtual void DoSomething() = 0;
    };
    
    class Point : public AbstractPoint
    {
    public:
        Point() = default;
        Point(const double data) : mData(data) {}
    
        void DoSomething(){}
    
        template<class TArchive>
        void serialize(TArchive& archive, const unsigned int version)
        {
            archive & mData;
        }
    
        double mData;
    };
    
    int main()
    {
        std::shared_ptr<Point> point(new Point(7.4));
    
        std::ofstream outputStream("test.txt");
        boost::archive::text_oarchive outputArchive(outputStream);
        outputArchive << point;
        outputStream.close();
    
        std::shared_ptr<Point> pointRead;
        std::ifstream inputStream("test.txt");
        boost::archive::text_iarchive inputArchive(inputStream);
        inputArchive >> pointRead;
    
        std::cout << pointRead->mData << std::endl;
        return 0;
    }
    

    Serialize derived class pointer, forwarding to parent (note nothing changes from the object case)

    #include <boost/archive/text_oarchive.hpp>
    #include <boost/archive/text_iarchive.hpp>
    #include <boost/serialization/shared_ptr.hpp>
    
    #include <fstream>
    
    class AbstractPoint
    {
    public:
        virtual ~AbstractPoint(){}
        virtual void DoSomething() = 0;
    
        template<class TArchive>
        void serialize(TArchive& archive, const unsigned int version)
        {
            archive & mParentData;
        }
    
        double mParentData = 3.1;
    };
    
    class Point : public AbstractPoint
    {
    public:
        Point() = default;
        Point(const double data) : mData(data) {}
    
        void DoSomething(){}
    
        template<class TArchive>
        void serialize(TArchive& archive, const unsigned int version)
        {
            archive & mData;
        }
    
        double mData;
    };
    
    int main()
    {
        std::shared_ptr<Point> point(new Point(7.4));
    
        std::ofstream outputStream("test.txt");
        boost::archive::text_oarchive outputArchive(outputStream);
        outputArchive << point;
        outputStream.close();
    
        std::shared_ptr<Point> pointRead;
        std::ifstream inputStream("test.txt");
        boost::archive::text_iarchive inputArchive(inputStream);
        inputArchive >> pointRead;
    
        std::cout << pointRead->mParentData << std::endl;
        std::cout << pointRead->mData << std::endl;
        return 0;
    }
    

    Serialize base class pointer (We now have to register the type of the derived class with the archives, as well as use boost::serialization::base_object)

    #include <boost/archive/text_oarchive.hpp>
    #include <boost/archive/text_iarchive.hpp>
    #include <boost/serialization/shared_ptr.hpp>
    #include <boost/serialization/base_object.hpp>
    
    #include <fstream>
    
    class AbstractPoint
    {
    public:
        virtual ~AbstractPoint(){}
        virtual void DoSomething() = 0;
    
        // This is required if we want to serialize an AbstractPoint pointer
        template<class TArchive>
        void serialize(TArchive& archive, const unsigned int version)
        {
            // do nothing
        }
    };
    
    class Point : public AbstractPoint
    {
    public:
        Point() = default;
        Point(const double data) : mData(data) {}
    
        void DoSomething(){}
    
        template<class TArchive>
        void serialize(TArchive& archive, const unsigned int version)
        {
            // Without this, we get unregistered void cast
            archive & boost::serialization::base_object<AbstractPoint>(*this);
    
            archive & mData;
        }
    
        double mData;
    };
    
    int main()
    {
        std::shared_ptr<AbstractPoint> point(new Point(7.4));
    
        std::ofstream outputStream("test.txt");
        boost::archive::text_oarchive outputArchive(outputStream);
        outputArchive.register_type<Point>();
        outputArchive << point;
        outputStream.close();
    
        std::shared_ptr<AbstractPoint> pointRead;
        std::ifstream inputStream("test.txt");
        boost::archive::text_iarchive inputArchive(inputStream);
        inputArchive.register_type<Point>();
        inputArchive >> pointRead;
    
        std::shared_ptr<Point> castedPoint = std::dynamic_pointer_cast<Point>(pointRead);
        std::cout << castedPoint->mData << std::endl;
        return 0;
    }
    
    0 讨论(0)
  • 2021-01-20 08:28
    • boost::serialization::base_object vs BOOST_SERIALIZATION_BASE_OBJECT_NVP

    The NVP wrapper is only ever required for archives that have element naming, like XML.

    Unless you use it, base_object<> is cleaner and simpler.

    • archive & mData; vs archive & BOOST_SERIALIZATION_NVP(mData);

    Ditto

    • The usefulness of BOOST_SERIALIZATION_ASSUME_ABSTRACT(AbstractPoint);

    I assume it will merely be an optimization - suppressing registered type information with each archive type, since you told the framework it will never be de-serializing instances of the type

    • Requiring serialize() for a class in the hierarchy that doesn't need to serialize anything.

    You don't need it, unless you need the type information about a polymorphic base there. When do you need that? When you need to de-serialize pointers of the base type.

    Hence, if you have

    struct A{ virtual ~A(); };
    struct B:A{};
    
    struct C:B{};
    struct D:B{};` 
    

    you will need serialization for A (but not B) if you (de)serialize A*. You will need serialization for B if you (de)serialize B*.

    Similarly, if your type is not polymorphic (virtual) or you don't use it as such, you don't need any base serialization (e.g. if you (de)serialize C or D directly).

    Finally, if you have struct A{}; struct B:A{}; there is no need to tell Boost Serialization about the base type at all, (you could just do the serialization from within B).

    Update in response to your samples:

    1. case1.cpp looks ok
    2. case2.cpp needs to call base serialization, of course; not necessarily using base_object because you require polymorphic serialization:

      template<class TArchive> void serialize(TArchive& archive, unsigned) {
          archive & boost::serialization::base_object<AbstractPoint>(*this)
                  & mData;
          // OR:
          archive & static_cast<AbstractPoint&>(*this) 
                  & mData;
          // OR even just:
          archive & mParentData 
                  & mData;
      }
      
    3. case3.cpp: indeed, it's exactly like case1, but with dynamic allocation and object tracking

    4. case4.cpp: is exactly like case1, but with dynamic allocation and object tracking; NB!! it requires explicitly serializing for the base!

      template<class TArchive> void serialize(TArchive& archive, unsigned) {
          archive & boost::serialization::base_object<AbstractPoint>(*this)
                  & mData;
      }
      
    5. case5.cpp: yes, but it's more typical to use the CLASS_EXPORT* macros from boost/serialization/export.hpp

    Bitrot insurance:

    • case1.cpp
    • case2.cpp
    • case3.cpp
    • case4.cpp
    • case5.cpp
    0 讨论(0)
提交回复
热议问题