简体   繁体   中英

boost::serialization with immutable abstract base and virtual inheritance

The code below is my current thinking to permit boost::serialization of an immutable abstract base with virtual inheritance. I hope I missed something and there is a simpler solution...?

As it stands, it raises a few questions:

  1. Is the comment in IObject::serialize valid?
  2. The comment in bs::save_construct_data for House seems to indicate a bug in boost::serialization . Is that correct, or is there a better way to do this?
  3. Is there a more elegant way to deserialise Building then the Deserialise function combined with a protected constructor?
  4. The result of (3) is that another Building implementation will necessitate a chunk of duplicated code. I suspect this will require a bit of CRTP to mitigate - any alternatives?
  5. How does one make this work if the virtual base contains data members? I suspect this is similar (or identical) to the Building::Deserialise .


    #include <fstream>
    #include <boost/archive/xml_oarchive.hpp>
    #include <boost/archive/xml_iarchive.hpp>
    #include <boost/serialization/export.hpp>

    namespace bs = boost::serialization;

    // IBase comes from an external library, and we are not interested in
    // serialising an IBase*.
    class IBase {
    public:
      virtual ~IBase(){};
    };

    class IObject : public virtual IBase {
    private:
      friend class bs::access;
      template <class Archive>
      void serialize(Archive &ar, const unsigned int version) {
        std::cout << "Called IObject's serialize\n";
        // IBase contains no members so there is no need to serialise it to/from the
        // archive. However, the inheritance relationship must be registered in
        // boost::serialization. We cannot use base_object to do this: It will try
        // to static_cast *this to IBase, but we might not have created the instance
        // yet, in which case there is no virtual table and a structured exception
        // will be generated.
        bs::void_cast_register<IObject, IBase>(static_cast<IObject *>(nullptr),
                                               static_cast<IBase *>(nullptr));
      }

    public:
      virtual ~IObject() {}
    };

    class IBuilding : public virtual IBase {
    private:
      friend class bs::access;
      template <class Archive>
      void serialize(Archive &ar, const unsigned int version) {
        std::cout << "Called IBuilding's serialize\n";
        bs::void_cast_register<IBuilding, IBase>(static_cast<IBuilding *>(nullptr),
                                                 static_cast<IBase *>(nullptr));
      }

    public:
      virtual ~IBuilding() {}
    };

    /* Tedious forward declarations to permit later friending. */
    class Building;
    class House;

    namespace boost {
    namespace serialization {
    template <class Archive>
    inline void save_construct_data(Archive &ar, const Building *t,
                                    const unsigned int version);
    template <class Archive>
    inline void save_construct_data(Archive &ar, const House *t,
                                    const unsigned int version);
    template <class Archive>
    inline void load_construct_data(Archive &ar, House *t,
                                    const unsigned int version);
    }
    }
    /* Tedious forward declarations end. */

    class Building : public IBuilding, public IObject {
    private:
      friend class bs::access;
      template <class Archive>
      void serialize(Archive &ar, const unsigned int version) {
        std::cout << "Called Building's serialize\n";
        // We can use base_object here because although the instance might not be
        // created, the memory has been allocated. Since there is no virtual
        // inheritance, the static_cast can succeed.
        ar &bs::make_nvp("IObject", bs::base_object<IObject>(*this));
        ar &bs::make_nvp("IBuilding", bs::base_object<IBuilding>(*this));
      }

      template <class Archive>
      inline friend void bs::save_construct_data(Archive &ar, const Building *t,
                                                 const unsigned int version);

      const double weight_;

    protected:
      const double height_;

      // The Members, associated constructor, and Deserialise facilitate recreating
      // this immutable base.
      struct Members {
        double weight_;
        double height_;
      };
      Building(const Members &members)
          : weight_(members.weight_), height_(members.height_) {}
      template <class Archive> const Members Deserialise(Archive &ar) const {
        double weight;
        double height;
        ar >> bs::make_nvp("weight_", weight) >> bs::make_nvp("height_", height);
        return {weight, height};
      }

    public:
      bool operator==(const Building &other) const {
        return weight_ == other.weight_ && height_ == other.height_;
      }

      virtual double Height() const = 0;
    };

    class House : public Building {
    private:
      template <class Archive>
      inline friend void bs::save_construct_data(Archive &ar, const House *t,
                                                 const unsigned int version);
      template <class Archive>
      inline friend void bs::load_construct_data(Archive &ar, House *t,
                                                 const unsigned int version);

      template <class Archive>
      explicit House(Archive &ar) : Building(Deserialise(ar)) {}

    public:
      House(double weight, double height) : Building({weight, height}) {}
      virtual double Height() const { return height_; }
    };

    BOOST_CLASS_EXPORT(House);

    namespace boost {
    namespace serialization {
    template <class Archive>
    inline void save_construct_data(Archive &ar, const Building *t,
                                    const unsigned int version) {
      std::cout << "Called Building's save_construct_data\n";
      ar << make_nvp("weight_", t->weight_) << make_nvp("height_", t->height_);
    }

    template <class Archive>
    inline void bs::save_construct_data(Archive &ar, const House *t,
                                        const unsigned int version) {
      std::cout << "Called House's save_construct_data\n";
      const auto &base = base_object<const Building>(*t);
      ar << make_nvp("Building", base);
      // ar << make_nvp("Building", &base); doesn't seem to work.
      // Serialising out a reference calls Building's serialize method, the
      // save_construct_data is only called for a pointer. This means we
      // have to call it explicitly.
      save_construct_data(ar, &base, version);
    }

    template <class Archive>
    inline void bs::load_construct_data(Archive &ar, House *t,
                                        const unsigned int version) {
      std::cout << "Called House's load_construct_data\n";
      ar >> make_nvp("Building", base_object<Building>(*t));
      ::new (t) House{ar};
    }
    }
    }

    int main() {
      const char *file_name = "house.ser";
      const bool save_first = true;

      const House house(45367, 2.43);
      std::cout << house.Height() << "\n";
      const IObject *iHouse = &house;

      if (save_first) {
        std::ofstream ofs(file_name);
        boost::archive::xml_oarchive oa(ofs);
        oa << BOOST_SERIALIZATION_NVP(iHouse);
      }

      IBuilding *iHouse2;
      {
        std::ifstream ifs(file_name);
        boost::archive::xml_iarchive ia(ifs);
        ia >> BOOST_SERIALIZATION_NVP(iHouse2);
      }

      if (dynamic_cast<const Building &>(*iHouse) ==
          dynamic_cast<const Building &>(*iHouse2))
        std::cout << "ok\n";
      else
        std::cout << "uh oh\n";

      return 0;
    }

I don't believe that the comment is correct. I believe that the void_cast_register is un-necessary since IBase is known to be a virtual base class of IObject .

Futhermore, if you don't add the serialization_support free functions for IBase , you won't be able to serialise/deserialise an IBase* , only an IObject* (although I think that's fine unless you are managing ownership through the IBase rather than the IObject ).

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM