Serialize Polymorph Interface

北城以北 提交于 2019-12-08 18:58:27

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
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!