Reputation: 1049
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
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.
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)
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)
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