Search code examples
c++serializationboostdeserialization

boost serialization and deserialization for different derived classes


Some classes have same variables and they all need serialization, so I wanna define an abstract class and it has two pure virtual fucntions serialize() and deserialize() so that these classes can inherit it and override the functions.

However, I found that the overridden functions are all duplicate code, like the following:

void Derived::serialize() {
    ...
    ofstream ofs(file, ios::binary);
    boost::archive::binary_oarchive oa(ofs);
    oa << (*this);
}

void Derived::deserialize() {
    ...
    ifstream ifs(file, ios::binary);
    boost::archive::bianry_iarchive ia(ifs);
    ia >> (*this);
}

How can I solve this problem?


Solution

  • I observe that you serialize *this which is Base&. Because that's not a pointer type, there's no way you can deserialize derived types. This makes me question what kind of class hierarchy you really have.

    I present to complete examples:

    1. Non-Virtual Hierarchy
    2. Virtual Hierarchy / Polymorphic Serialization

    1. Non-Virtual Hierarchy

    You could employ CRTP to do something that does depend on the derived type:

    Live On Coliru

    #include <boost/archive/binary_oarchive.hpp>
    #include <boost/archive/binary_iarchive.hpp>
    #include <boost/serialization/serialization.hpp>
    #include <boost/serialization/array.hpp>
    #include <fstream>
    
    template <typename Derived> class Base {
      public:
        void serialize() const
        {
            //...
            std::ofstream ofs(file, std::ios::binary);
            boost::archive::binary_oarchive oa(ofs);
            oa << as_derived();
        }
    
        void deserialize() {
            //...
            std::ifstream ifs(file, std::ios::binary);
            boost::archive::binary_iarchive ia(ifs);
            ia >> as_derived();
        }
    
      protected:
        std::string file = "base.bin";
    
      private:
        int base_data = 42;
        Derived& as_derived()             { return static_cast<Derived&>(*this); } ;
        Derived const& as_derived() const { return static_cast<Derived&>(*this); } ;
    
        friend class boost::serialization::access;
        template <typename Ar> void serialize(Ar& ar, unsigned)
        {
            ar
                & base_data
                & as_derived().common_derived_data
                ;
        }
    };
    
    struct Derived1 : Base<Derived1> {
      private:
        friend class Base;
        std::array<float, 30> common_derived_data = {1,1,1,1};
    };
    
    struct Derived2 : Base<Derived2> {
      private:
        friend class Base;
        std::array<float, 30> common_derived_data = {2,2,2,2};
    };
    
    int main() {
        Derived1 x;
        Derived2 y;
    
        x.serialize();
        system("xxd base.bin");
        x.deserialize();
    
        y.serialize();
        system("xxd base.bin");
        x.deserialize();
    }
    

    Note:

    This is no longer a virtual hierarchy. Polymorphic types impose constraints on Boost Serialization. You will want to stick to the Boost approach. Perhaps use a simple free function template to help out with repeated code?

    2. Virtual Hierarchy / Polymorphic Serialization

    Example:

    Live On Coliru

    Note:

    • serialization MUST be through a pointer
    • serialization MUST be through a pointer to Base (or at least the same type as serialized always, so a base class is the only thing that makes sense)
    • types MUST be registered (or assumed abstract), see BOOST_CLASS_EXPORT et al.
    • the repeated_serialization_code function template is now the home to repeated serialization code.
    • note that that MUST include base_object
    • This can be deduced const/non-const depending on call context.
    #include <boost/archive/binary_oarchive.hpp>
    #include <boost/archive/binary_iarchive.hpp>
    #include <boost/serialization/serialization.hpp>
    #include <boost/serialization/base_object.hpp>
    #include <boost/serialization/unique_ptr.hpp>
    #include <boost/serialization/array.hpp>
    #include <boost/serialization/export.hpp>
    #include <fstream>
    
    class Base {
      public:
        using Ptr = std::unique_ptr<Base>;
    
        virtual ~Base() = default;
    
        static void serialize(std::string file, Ptr const& ptr) 
        {
            //...
            std::ofstream ofs(file, std::ios::binary);
            boost::archive::binary_oarchive oa(ofs);
            oa << ptr;
        }
    
        static Ptr deserialize(std::string file) {
            Ptr ptr;
            //...
            std::ifstream ifs(file, std::ios::binary);
            boost::archive::binary_iarchive ia(ifs);
            ia >> ptr;
    
            return ptr;
        }
    
      protected:
        int base_data = 42;
        friend class boost::serialization::access;
        template <typename Ar> void serialize(Ar& ar, unsigned) { ar& base_data; }
    
        template <typename Ar, typename This>
        static inline void repeated_serialization_code(Ar& ar, This& self, unsigned)
        {
            ar & boost::serialization::base_object<Base>(self)
               & self.common_derived_data;
        }
    };
    
    struct Derived1 : Base {
      private:
        friend class Mixin;
        std::array<float, 30> common_derived_data = {1,1,1,1};
    
        friend class boost::serialization::access;
        friend class Base;
        template <typename Ar> void serialize(Ar& ar, unsigned v) {
            repeated_serialization_code(ar, *this, v);
        }
    };
    
    struct Derived2 : Base {
      private:
        friend class Mixin;
        std::array<float, 30> common_derived_data = {2,2,2,2};
    
        friend class boost::serialization::access;
        friend class Base;
        template <typename Ar> void serialize(Ar& ar, unsigned v) {
            repeated_serialization_code(ar, *this, v);
        }
    };
    
    BOOST_SERIALIZATION_ASSUME_ABSTRACT(Base)
    BOOST_CLASS_EXPORT(Derived1)
    BOOST_CLASS_EXPORT(Derived2)
    
    int main() {
        Base::Ptr x = std::make_unique<Derived1>();
        Base::Ptr y = std::make_unique<Derived2>();
    
        Base::serialize("x.bin", x);
        system("xxd x.bin");
        auto x2 = Base::deserialize("x.bin");
    
        Base::serialize("y.bin", y);
        system("xxd y.bin");
        auto y2 = Base::deserialize("y.bin");
    }