Ra'Jiska
Ra'Jiska

Reputation: 1049

Serialize Polymorph Interface

I am looking to serialize a polymorphed class from its associated interface.

Here is what I have found this question, which seems to do what I need to: How to create a interface for serialization in Boost Serialization?

However the serialization is done from the class itself and not the interface. What I have got so far:

INetworkMessage.hpp

using PolyArchive = boost::variant<
    boost::archive::polymorphic_oarchive &,
    boost::archive::polymorphic_iarchive&>;

class INetworkMessage {
    public:
    INetworkMessage() = default;
    virtual ~INetworkMessage() = default;

    virtual void serialize(PolyArchive ar, unsigned int version) = 0;
};

namespace visitor {
    template <typename F> struct wrap_visitor : boost::static_visitor<>
    {
        wrap_visitor(F const& f) : f_(f) { }
        wrap_visitor(F&& f)      : f_(std::move(f)) { }

        template<typename... T> void operator()(T&&... t) const
        {
            f_(std::forward<T>(t)...);
        }

    private:
        F f_;
    };

    template <typename F>
    wrap_visitor<F> make_visitor(F&& f)
    {
        return std::forward<F>(f);
    }
}

BOOST_SERIALIZATION_ASSUME_ABSTRACT(INetworkMessage)

NetworkMessage.hpp

class NetworkMessage : public INetworkMessage {
    public:
    struct Header
    {
        enum MessageType
        {
            TYPE_LOGIN,
            TYPE_LOGOUT,
            TYPE_CONTROL,
            TYPE_VOICE
        };
        unsigned long long int to;
        unsigned long long int from;
        enum MessageType type;
        size_t size;
    };

    NetworkMessage();
    NetworkMessage(const struct NetworkMessage::Header &header);
    virtual ~NetworkMessage() = 0;

    struct NetworkMessage::Header &getHeader();
    virtual void serialize(PolyArchive ar, unsigned int) = 0;

    private:
    struct Header header;
};

BOOST_SERIALIZATION_ASSUME_ABSTRACT(NetworkMessage)

NetworkMessageLogin.hpp

class NetworkMessageLogin : public NetworkMessage {
    public:
    NetworkMessageLogin();
    NetworkMessageLogin(const struct NetworkMessage::Header &header);
    ~NetworkMessageLogin();

    void setId(unsigned long long int id) noexcept;
    unsigned long long int getId() const noexcept;
    void setName(const std::string &name) noexcept;
    const std::string &getName() const noexcept;

    virtual void serialize(PolyArchive ar, unsigned int) override;

    protected:
    unsigned long long int id;
    std::string name;
};

And here is what I'd like to be able to do:

struct NetworkMessage::Header header = { 0, 1, NetworkMessage::Header::TYPE_LOGIN, 4 };
NetworkMessageLogin msg(header);
msg.setId(1245);
msg.setName("Test");
INetworkMessage *interface = new NetworkMessageLogin(msg);

std::stringstream ss;
boost::archive::polymorphic_text_oarchive oa(ss);
oa << interface;
std::cout << "Serial: " << ss.str() << std::endl;

With this attempt I get an exception what(): unregistered class - derived class not registered or exported.

I've tried using the CLASS_BOOST_EXPORT on NetworkMessageLogin, but without success, I just had a bunch of errors.

How can I achieve serialization from the interface of a class implementing my serialization method ?

Upvotes: 2

Views: 285

Answers (1)

sehe
sehe

Reputation: 393759

You're mixing dynamic polymorphism (virtuals) and static polymorphism (generic template functions).

That's going to be tricky. In particular in this case I think you need to guarantee that no concrete archive type other than the polymorphic ones are visible at the time of instantiation of the class-export machineries. Because the POI may be at the end of the translation unit (TU), you may have to separate the export KEY/IMPLEMENTATION macros, and put the IMPLEMENTATION bits in a separate TU.

Here's a proof of concept that compiled: Live On Wandbox

WARNING That code is broken!

The problem with this is that it sublty breaks Boost Serialization's support for polymorphic serialized types.

Most importantly, the base_object parsers will be inadvertantly redirected to the do_serialize implementation of the most-derived class, making it so that the most-derived do_serialize runs more than once, and all the base-class serialization isn't run.

So to make it actually work you need to account for it and move all serialization into the base class. Now, you have to manually take care of registering conversion and registration functions, because base_object cannot be used unless you want to end up with all details repeated inside your output.

Live On Wandbox

  1. network.h

    #pragma once
    #include <boost/archive/polymorphic_oarchive.hpp>
    #include <boost/archive/polymorphic_iarchive.hpp>
    #include <boost/serialization/export.hpp>
    #include <boost/serialization/base_object.hpp>
    #include <boost/variant.hpp>
    
    struct INetworkMessage {
        virtual ~INetworkMessage() = default;
    
      protected:
        using Archive = boost::variant<boost::archive::polymorphic_oarchive&, boost::archive::polymorphic_iarchive&>;
        virtual void do_serialize(Archive, unsigned) = 0;
    
      private:
        friend class boost::serialization::access;
        template<class Ar> void serialize(Ar& ar, unsigned version) {
            this->do_serialize(Archive{ar}, version);
        }
    };
    
    BOOST_SERIALIZATION_ASSUME_ABSTRACT(INetworkMessage)
    
    class NetworkMessage : public INetworkMessage {
      public:
        struct Header {
            enum MessageType { TYPE_LOGIN, TYPE_LOGOUT, TYPE_CONTROL, TYPE_VOICE, TYPE_UNSPECIFIED };
            unsigned long long int to   = 0;
            unsigned long long int from = 0;
            enum MessageType type       = TYPE_UNSPECIFIED;
            std::size_t size            = 0;
    
            template<class Ar> void serialize(Ar& ar, unsigned) {
                ar & to & from & type & size;
            }
        };
    
        NetworkMessage() = default;
        NetworkMessage(Header const &header) : header(header) {}
        NetworkMessage::Header &getHeader();
    
      private:
        Header header;
    
      protected:
        virtual void do_serialize(Archive ar, unsigned) override {
            boost::apply_visitor([=](auto& ar) {
                boost::serialization::void_cast_register<NetworkMessage, INetworkMessage>(this, this);
                ar & header;
           }, ar);
        }
    };
    
    class NetworkMessageLogin : public NetworkMessage {
      public:
        NetworkMessageLogin(const NetworkMessage::Header &header = {}) : NetworkMessage(header) {}
    
        void                   setId(unsigned long long int id) noexcept  { this->id = id;     } 
        unsigned long long int getId() const                    noexcept  { return id;         } 
        void                   setName(const std::string &name) noexcept  { this->name = name; } 
        const std::string&     getName() const                  noexcept  { return name;       } 
    
      protected:
        unsigned long long int id;
        std::string name;
    
        virtual void do_serialize(Archive ar, unsigned version) override {
            boost::apply_visitor([=](auto& ar) {
                boost::serialization::void_cast_register<NetworkMessageLogin, NetworkMessage>(this, this);
                NetworkMessage::do_serialize(ar, version);
                ar & id & name;
            }, ar);
        }
    };
    
    BOOST_CLASS_EXPORT_KEY(INetworkMessage)
    BOOST_CLASS_EXPORT_KEY(NetworkMessage)
    BOOST_CLASS_EXPORT_KEY(NetworkMessageLogin)
    
  2. network.cpp

    #include "network.h"
    #include <boost/serialization/string.hpp>
    
    BOOST_CLASS_EXPORT_IMPLEMENT(INetworkMessage)
    BOOST_CLASS_EXPORT_IMPLEMENT(NetworkMessage)
    BOOST_CLASS_EXPORT_IMPLEMENT(NetworkMessageLogin)
    
  3. test.cpp

    #include "network.h"
    #include <boost/archive/polymorphic_text_oarchive.hpp>
    #include <boost/archive/polymorphic_text_iarchive.hpp>
    
    #include <iostream>
    
    INetworkMessage* sample_msg() {
        NetworkMessage::Header header { 0, 1, NetworkMessage::Header::TYPE_LOGIN, 4 };
        auto msg = new NetworkMessageLogin(header);
        msg->setId(1245);
        msg->setName("Test");
    
        return msg;
    }
    
    int main() {
    
        std::stringstream ss;
        {
            boost::archive::polymorphic_text_oarchive oa(ss);
            INetworkMessage* interface = sample_msg();
            oa << interface;
            delete interface;
        }
    
        std::cout << "Serial: " << ss.str() << std::endl;
    
        {
            boost::archive::polymorphic_text_iarchive ia(ss);
            INetworkMessage* roundtripped = nullptr;
            ia >> roundtripped;
    
            if (auto login = dynamic_cast<NetworkMessageLogin*>(roundtripped)) {
                std::cout << "Name: " << login->getName() << "\n";
                std::cout << "Id:   " << login->getId() << "\n";
            }
    
            delete roundtripped;
        }
    }
    

Build with e.g.

g++ -std=c++14 network.cpp test.cpp -o ./test.exe -lboost_serialization

Prints

Serial: 22 serialization::archive 16 0 19 NetworkMessageLogin 1 0
0 0 0 0 1 0 4 1245 4 Test

Name: Test
Id:   1245

Upvotes: 1

Related Questions