Boost deserialize a derived class to base class po

2019-07-14 07:37发布

问题:

Please help me deserialize a derived class to base-class pointer. I attach the complete source code example.

request.hpp (no pair cpp file)

#ifndef REQUEST_HPP
#define REQUEST_HPP

#include <memory>
#include <string>

#include <boost/archive/xml_oarchive.hpp>
#include <boost/archive/xml_iarchive.hpp>

namespace demo {
namespace common {

        class request {
        public:
            static const int INVALID_ID = -42;

            request() 
                : id_(INVALID_ID), timestamp_(0), source_ip_("unknown") {};

            request(int id, long timestamp, const std::string& source_ip) 
                : id_(id), timestamp_(timestamp), source_ip_(source_ip) {};

            virtual ~request() {};

            int id() const { return id_; }
            long timestamp() const { return timestamp_; }
            std::string source_ip() const { return source_ip_; }



        protected:
            int id_;
            long timestamp_;
            std::string source_ip_;



        private:
            friend class boost::serialization::access;

            template<class Archive>
            void serialize(Archive& ar, const unsigned version) {
                ar & BOOST_SERIALIZATION_NVP(id_);
                ar & BOOST_SERIALIZATION_NVP(timestamp_);
                ar & BOOST_SERIALIZATION_NVP(source_ip_);
            }

        };

        typedef std::shared_ptr<request> request_ptr;

    }
};

#endif

command.hpp (derived class)

#ifndef COMMAND_HPP
#define COMMAND_HPP

#include <memory>
#include <string>

#include <boost/serialization/export.hpp>

#include <demo/common/request.hpp>

namespace demo {
    namespace common {

            class command : public request {
            public:
                command(): name_("untitled") {};
                explicit command(const std::string& name) : name_(name) {};
                virtual ~command() {};

                virtual void execute();

                std::string name() const { return name_; }

            protected:
                std::string name_;

            private:
                friend class boost::serialization::access;

                template<class Archive>
                void serialize(Archive& ar, const unsigned version) {
                    ar & BOOST_SERIALIZATION_BASE_OBJECT_NVP(request);
                    ar & BOOST_SERIALIZATION_NVP(name_);
                }

            };

            typedef std::shared_ptr<command> command_ptr;

        }
};

BOOST_CLASS_EXPORT_KEY(demo::common::command)


#endif

command.cpp

#include "command.hpp"
#include <iostream>

BOOST_CLASS_EXPORT_IMPLEMENT(demo::common::command)

namespace demo {
    namespace common {

            void command::execute() {
                std::cout << "  I am '" + name_ +"' and I am executing..." << std::endl;
            }

    }
};

serializer.hpp

#ifndef SERIALIZER_HPP
#define SERIALIZER_HPP

#include <sstream>
#include <string>

/* classes to serialize */
#include <demo/common/request.hpp>
#include <demo/common/command.hpp>

namespace demo {
    namespace common {

        class serializer {
        public:
            serializer() : {};

            template<typename T>
            std::string serialize(const T& t){  
                std::stringstream stream;
                boost::archive::xml_oarchive archive(stream);
                archive << BOOST_SERIALIZATION_NVP(t);
                std::string serialized = stream.str();

                return serialized;
            }


            template<typename T>
            void deserialize(const std::string& serialized, T& t) {
                std::stringstream stream(serialized);
                boost::archive::xml_iarchive archive(stream);
                archive >> BOOST_SERIALIZATION_NVP(t);
            }
        };

    }
}

#endif

sample usage

#include <iostream>

#include <demo/common/serializer.hpp>
#include <demo/common/command.hpp>


using namespace std;
using namespace demo::common;

int main(){
    serializer serializer_;

    command r("123"); // <-- (1) my desired way of declaring
    //request* r = new command("123"); <-- (2) replacing with this makes all work!
    //command* r = new command("123"); <-- (3) replacing with this crashes the app, like (1)
    std::string s = serializer_.serialize(r);
    std::cout << s << std::endl;
    request* rr = nullptr;
    serializer_.deserialize(s, rr); //this throws an exception

    command* rrr = dynamic_cast<command*>(rr);

    rrr->execute();
}

I thought I did everything that needs to be done, archives included before any class export, all default constructors initialize members..

Note that the serializable classes and the serializer are compiled to a lib file. Then that lib is used in two sub-projects that have access to the headers and have that lib linked. They use those classes to communicate with each other, they send serialized objects over network.

Why can't I deserialize a derived class to a base class pointer? I am using Boost 1.51 and VC11.

回答1:

You're probably getting an input_stream_error in your demo and unregistered_class exception when using your library. This is caused by the way boost is registering the classes, in your case, automatically.

It appears that the automatic registration process gets confused when you serialize a derived object and deserialize to its base, despite the use of the BOOST_CLASS_EXPORT* macros.

However, you can register the classes explicitly before you perform any i/o operation on the archive:

// ...
boost::archive::xml_iarchive archive(stream);
// register the class(es) with the archive
archive.template register_type<command>();
archive >> BOOST_SERIALIZATION_NVP(t);
// ...

Use the same order of registration when serializing. This makes the export macros superfluous.



回答2:

Problems:

The two major things I found finicky and not documented enough about Boost::serialization that caused me issues are as follows:

  1. Serialization / deserialization of objects on the stack mixed with objects on the heap. For example if you serialize from a object on the stack then attempt to deserialize to a pointer (e.g. invoke your load_construct_data<>) an exception may occur. Same with the reverse scenario.
  2. Not having your exports linked in properly. If you create serialization templates/classes and place them in a .lib for example, it seems the exports may not be properly linked in / exposed. This goes for linking in and then using from a shared object/DLL.

Solutions:

For #1, I've found it easiest to make a rule of always serializing/deserializing to/from pointers. Even objects on the stack can use a temporary pointer when serializing to allow for this rule. For example:

// serialize
MyObject myobj;
std::ostringstream oss;
boost::archive::text_oarchive oa(oss);
MyObject* myObjPtr = &myObj;
oa << myObjPtr; // this is different than oa << myObj!!
std::string serialized = oss.str();

// deserialize
MyObject* myNewObjPtr;
std::stringstream iss(serialized);
boost::archive::text_iarchive ia(iss);
ia >> myNewObjPtr; // invokes new, don't forget to delete (or use smart ptrs!!!)

For #2, simply create a .cpp file that contains all of your exports. Link this CPP into your module(s) directly. In other words, you'll have a .cpp with a bunch of BOOST_CLASS_EXPORT_IMPLEMENT():

BOOST_CLASS_EXPORT_IMPLEMENT(MyObject);
// ...

More Complete Example:
Below is a more complete example showing some of the serialization tricks using non-intrusive templates. Intrusive member methods will be very similar:

MyObject.h

// Can be broken into MyObject.h, MyObject.cpp, MyObjectSerialization.h for example as well. 
// This stuff can live in your .lib
#include <boost/serialization/export.hpp>
#include <boost/archive/text_oarchive.hpp>
#include <boost/archive/text_iarchive.hpp>
// assume this class contains GetSomeMember() returning SomeMemberType
class MyObject { /* ... */ };
BOOST_CLASS_EXPORT_KEY(MyObject);
namespace boost { namespace serialization {
  template<class Archive>
      void serialize(Archive& ar, MyObject& myObj, const unsigned int version)
  {
      ar & myObj.m_someMember;
  }

  template<class Archive>
      inline void save_construct_data(Archive& ar, const MyObject* myObj, const unsigned int version)
  {
    ar & boost::serialization::make_nvp("SomeMemberType", static_cast<const SomeMemberType&>(myObj->GetSomeMember()));
  }

  template<class Archive>
  inline void load_construct_data(Archive& ar, MyObject* myObj, const unsigned int version)
  {
      SomeMemberType t;
      ar & boost::serialization::make_nvp("SomeMemberType", t);
      ::new(myObj)MyObject(t);
  }
} } // end boost::serialization ns

MyObjectExports.cpp

// This file must be explicitly linked into your module(s) that use serialization.
// This means your executable or shared module/DLLs
#include <boost/serialization/export.hpp>
#include <boost/archive/text_oarchive.hpp>
#include <boost/archive/text_iarchive.hpp>
#include "MyObject.h"

BOOST_CLASS_EXPORT_IMPLEMENT(MyObject);