2

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.

emesx
  • 12,555
  • 10
  • 58
  • 91
  • Boost serialization is very picky. Ensure your exports are linked in to the same module (assuming all of your code above is in your executable you're OK). Also, I've had little luck with mixing objects on the stack / heap -- Instead, I've found it easier to *always* serialize and de-serialize to/from pointers (wrap in smart pointers). – NuSkooler Oct 26 '12 at 15:47
  • Waaait. The `request`, `command` and `serializer` classes are in one project, compiled to `.lib` that is then used in a second project in same VS solution ... is this a bad smell? – emesx Oct 26 '12 at 16:03
  • Where `serializer_` is defined? Please post a complete compilable `main()`. – n. m. could be an AI Oct 26 '12 at 16:12
  • here you go, a complete main :) – emesx Oct 26 '12 at 16:39
  • This might be an old boost::serialization bug. I get [this](https://www.google.com/search?q=boost%3A%3Aarchive%3A%3Adetail%3A%3Abasic_iarchive_impl%3A%3Aload_preamble+segfault&oq=boost%3A%3Aarchive%3A%3Adetail%3A%3Abasic_iarchive_impl%3A%3Aload_preamble+segfault) segmentation fault. – n. m. could be an AI Oct 26 '12 at 16:59
  • Any idea how to fix it? Removing the virtual dtor in ***request*** is not an option :( – emesx Oct 26 '12 at 17:23
  • I had a similar setup that was having issues. Keeping my serialization classes in the .lib was fine, but I moved and explicitly linked in the _exports_ into exe/module (e.g. bring in .cpp's). And again, do make sure to go to/from pointers. Even a temp pointer to an object on the stack seems fine. – NuSkooler Oct 26 '12 at 20:22
  • @NuSkooler could you explain in more detail, what I could do? Not necessarily in a comment, a full answer would be great.. – emesx Oct 27 '12 at 11:32

2 Answers2

4

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);
NuSkooler
  • 5,391
  • 1
  • 34
  • 58
  • Your ideas helped me move a little bit. Imagine this situation: I have a shared templated serializer. In one sub-project I create a `request* r = new command()`, but in the second I don't - I deserialize everything to a `request` pointer. The problem is that if I never create a `command` object in the second sub-project, the appropriate templates don't get instantiated.. (?). If I add `command* c = new command();` anywhere in the second sub-project's code then it all wokrs. It looks like the classes are unregistered. So... is it possible to force the registration? – emesx Oct 29 '12 at 17:10
  • 1
    Added more to the example. Perhaps that will help. – NuSkooler Oct 29 '12 at 19:30
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.

Anonymous Coward
  • 6,186
  • 1
  • 23
  • 29
  • Can you explain why this happens? Why doesn't boost export macro work? – emesx Nov 01 '12 at 10:21
  • At this point i can only guess. When using the macros, the information stored in the archive (here xml) differs based on the type serialized; for ptr to base, information like class_name is stored and the class_id's are reversed. This causes the stream_error when deserializing. It may have to do with the order in which the types are deduced. Unfortunately i can't be more specific without dissecting the boost::serialization library. – Anonymous Coward Nov 01 '12 at 11:26
  • Serializing a derived obj and deserializing to its base ptr (using export macros) wouldn't even work in their unit test (test_dll_exported.cpp) if you modified it to do that. The documentation also states, that the export macros are intended for serialization through base ptrs [only?]. Furthermore, derived to base serialization is NOT in the unit tests. To be on the safe side, i would only de/serialize base pointers. – Anonymous Coward Nov 02 '12 at 14:00
  • This seems to work - de/serializing everything to/via base class pointers. Thanks, I hope I won't run into any more problems.. – emesx Nov 04 '12 at 09:39
  • @AnonymousCoward What do you mean by "Use the same order of registration when serializing. This makes the export macros superfluous."? Same order as what? – David Doria Feb 23 '16 at 21:36