Search code examples
c++serializationboost

Boost serialization base class without default constructor


How to serialize/deserialize derived class inheriting base class without default constructor?
Please offer serializing boost functions for the following classes

struct Base
{
  Base(int b) : b(b) {}
  const int b;
}

struct Derived : public Base
{
  Derived(float d, int b) : Base(b), d(d) {}
  const float d;
}

Solution

  • Your use-case straddles two of the "special considerations" documented by Boost Serialization:

    Note that I'm going to assume you want dynamic polymorphism, and to get this you need at least a virtual destructor. If you don't you will end up with Undefined Behaviour.


    Combining the two for your example:

    Live On Coliru

    #include <boost/archive/text_oarchive.hpp>
    #include <boost/archive/text_iarchive.hpp>
    #include <boost/serialization/base_object.hpp>
    #include <boost/serialization/serialization.hpp>
    #include <boost/serialization/export.hpp>
    #include <iostream>
    
    struct Base {
        Base(int b) : b(b) {}
        virtual ~Base() = default;
        const int b;
    };
    
    namespace boost::serialization {
        template <typename Ar> inline void serialize(Ar&, Base&, unsigned) {}
    
        template <typename Ar>
        inline void save_construct_data(Ar& ar, Base const* p, unsigned) {
            // save data required to construct instance
            ar << p->b;
        }
    
        template <typename Ar>
        inline void load_construct_data(Ar& ar, Base* p, unsigned) {
            int attribute;
            ar >> attribute;
            // invoke inplace constructor to initialize instance
            ::new (p) Base(attribute);
        }
    } // namespace boost::serialization
    
    struct Derived : public Base {
        Derived(float d, int b) : Base(b), d(d) {}
        const float d;
    };
    
    BOOST_CLASS_EXPORT(Base)
    BOOST_CLASS_EXPORT(Derived)
    
    namespace boost::serialization {
        template <typename Ar> inline void serialize(Ar& ar, Derived& d, unsigned) {
            ar & boost::serialization::base_object<Base>(d);
        }
        template <typename Ar>
        inline void save_construct_data(Ar& ar, Derived const* p, unsigned) {
            // save data required to construct instance
            ar & p->b & p->d;
        }
    
        template <typename Ar>
        inline void load_construct_data(Ar& ar, Derived* p, unsigned) {
            int b;
            float d;
    
            ar & b & d;
            // invoke inplace constructor to initialize instance
            ::new (p) Derived(d, b);
        }
    } // namespace boost::serialization
    
    std::string save(Base* b) {
        std::ostringstream oss;
        {
            boost::archive::text_oarchive oa(oss);
            oa << b;
        }
        return oss.str();
    }
    
    Base* load(std::string txt) {
        std::istringstream iss(std::move(txt));
        boost::archive::text_iarchive ia(iss);
        Base* b = nullptr;
        ia >> b;
        return b;
    }
    
    int main() {
        for (Base* object :
             {
                 new Base(-99),
                 static_cast<Base*>(new Derived(3.14, 42)),
             }) //
        {
            std::cout << "----\n";
            Base* roundtrip = load(save(object));
    
            delete object;
            std::cout << "roundtrip: b=" << roundtrip->b;
            if (auto* as_derived = dynamic_cast<Derived const*>(roundtrip)) {
                std::cout << ", d=" << as_derived->d;
            }
            std::cout << "\n";
    
            delete roundtrip;
        }
    }
    

    Prints

    ----
    roundtrip: b=-99
    ----
    roundtrip: b=42, d=3.14
    

    SAFETY FIRST

    Of course, don't use raw new/delete:

    1. using unique_ptr Live On Coliru
    2. using shared_ptr (note the dynamic_pointer_cast) Live On Coliru