boost serialize polymorphic class - c++

With the following example I attempting to learn a few new to me concepts.
abstraction
polymorphic classes
factory programming.
boost serialization
The nuances of how pointers behave are still something I am working to figure out.
Here is a small program that I have written to show you the issue I am struggling to understand.
When I unserialize the polymorphic object below I only get an object created from the default constructor.
TodoFactory::retrieveATodo is not recreating the object from the serialized data. This is displayed by the output of "unserialzed command" in that function.
Here is the full program:
#include <string>
#include <bitset>
#include <boost/serialization/string.hpp>
#include <sstream>
#include <boost/archive/text_oarchive.hpp>
#include <boost/archive/text_iarchive.hpp>
#include <boost/serialization/export.hpp>
//abstract class
class aTodo{
private:
friend class boost::serialization::access;
protected:
const char _initType;
public:
aTodo():_initType(0x00){};
aTodo(const char type):_initType(type){};
std::string oarchive(){
std::ostringstream archive_stream;
{
boost::archive::text_oarchive archive(archive_stream);
archive << *this;
}
archive_stream.flush();
std::string outbound_data=archive_stream.str();
std::string foutbound_data;
foutbound_data=_initType;
foutbound_data+=outbound_data;
std::cout << "length: " << foutbound_data.length() << std::endl;
return foutbound_data;
}
virtual void Do()=0;
virtual ~aTodo(){};
template<class Archive>
void serialize(Archive & ar, unsigned int version){
ar & _initType;
};
char getInitType(){return _initType;};
};
// include headers that implement a archive in simple text format
class todoExec:public aTodo{
private:
friend class boost::serialization::access;
template<class Archive>
void serialize(
Archive& ar,
unsigned int version
)
{
std::cout << "serialize todoexec" << std::endl;
//base
boost::serialization::base_object<aTodo>(*this);
//derived
ar & _command;
}
std::string _command;
protected:
public:
static const char _TYPE=0x01;
todoExec():aTodo(_TYPE){};
todoExec(std::string command):aTodo(_TYPE){_command=command;};
void Do(){std::cout << "foo" << std::endl;};
virtual ~todoExec(){};
std::string getCommand(){return _command;};
};
class todoFactory{
private:
protected:
public:
std::unique_ptr<aTodo> retrieveAtodo(const std::string & total){
std::cout << "here" << std::endl;
char type=total.at(0);
std::cout << "bitset: " << std::bitset<8>(type) << std::endl;
std::string remainder=total.substr(1);
if(type==0x01){
std::cout << "remainder in retrieve: " << remainder << std::endl;
std::unique_ptr<todoExec> tmp(new todoExec());
std::stringstream archive_stream(remainder);
std::cout << "stream remainder: " << archive_stream.str() << std::endl;
{
boost::archive::text_iarchive archive(archive_stream);
archive >> *tmp;
}
std::cout << "unserialized type: " << std::bitset<8>(tmp->getInitType()) << std::endl;
std::cout << "unserialized command: " << tmp->getCommand() << std::endl;
return std::move(tmp);
}
};
std::unique_ptr<aTodo> createAtodo(char type,std::string command){
if(type==0x01){
std::unique_ptr<todoExec> tmp(new todoExec(command));
return std::move(tmp);
}
};
};
int main(){
char mtype=0x01;
std::string dataToSend = "ls -al /home/ajonen";
std::unique_ptr<todoFactory> tmpTodoFactory; //create factory
std::unique_ptr<aTodo> anExecTodo=tmpTodoFactory->createAtodo(mtype,dataToSend); //create ExecTodo from factory
if(auto* m = dynamic_cast<todoExec*>(anExecTodo.get()))
std::cout << "command to serialize: " << m->getCommand() << std::endl;
//archive
std::string remainder = anExecTodo->oarchive();
//now read in results that are sent back
std::unique_ptr<aTodo> theResult;
theResult=tmpTodoFactory->retrieveAtodo(remainder);
std::cout << "resultant type: " << std::bitset<8>(theResult->getInitType()) <<std::endl;
if(auto* d = dynamic_cast<todoExec*>(theResult.get()))
std::cout << "resultant Command: " << d->getCommand() <<std::endl;
return 0;
}
And here is the program output:
command to serialize: ls -al /home/ajonen
length: 36
here
bitset: 00000001
remainder in retrieve: 22 serialization::archive 12 0 0 1
stream remainder: 22 serialization::archive 12 0 0 1
serialize todoexec
unserialized type: 00000001
unserialized command:
resultant type: 00000001
resultant Command:
I also found out that the serialize method is only being called for the base class aTodo. I am going to need to find a way to make that virtual, but it is a template function. That is problem number one.

Your program has Undefined Behaviour because all of the factory functions have missing returns.
Next up, using a type code in a class hierarchy is a Design Smell.
Concrete hints:
serialize the same type as you deserialize
let Boost Serialization handle the polymorphism (otherwise, why do you use polymorphism, or why do you use Boost Serialization?). Boost handles it when you serialize (smart) pointers to base.
register your classes (BOOST_CLASS_EXPORT). You had included the header but didn't use it.
There doesn't seem to be a reason for the factory. Consider dropping it
In general, remove cruft. it's hard to think when your code is too noisy. Here's my cleaned up version:
Live On Coliru
This also uses Boost for streaming to string without unnecessary copying.
#include <boost/archive/text_iarchive.hpp>
#include <boost/archive/text_oarchive.hpp>
#include <boost/serialization/export.hpp>
#include <boost/serialization/unique_ptr.hpp>
#include <boost/iostreams/device/back_inserter.hpp>
#include <boost/iostreams/device/array.hpp>
#include <boost/iostreams/stream.hpp>
namespace Todo
{
struct BaseTodo {
using Ptr = std::unique_ptr<BaseTodo>;
virtual ~BaseTodo() = default;
virtual void Do() = 0;
virtual unsigned getInitType() { return 0x00; };
private:
friend class boost::serialization::access;
template <class Ar> void serialize(Ar &, unsigned) {}
};
class Exec : public BaseTodo {
public:
Exec(std::string const &command = "") : _command(command){};
virtual unsigned getInitType() { return 0x01; };
virtual void Do() { std::cout << "foo: " << getCommand() << std::endl; };
std::string getCommand() const { return _command; };
private:
friend class boost::serialization::access;
template <class Archive> void serialize(Archive &ar, unsigned) {
boost::serialization::base_object<BaseTodo>(*this);
ar &_command;
}
std::string _command;
};
}
//BOOST_CLASS_EXPORT(BaseTodo)
BOOST_SERIALIZATION_ASSUME_ABSTRACT(Todo::BaseTodo)
BOOST_CLASS_EXPORT(Todo::Exec)
namespace Todo
{
class Factory {
Factory() = default;
public:
using Ptr = BaseTodo::Ptr;
using FactoryPtr = std::shared_ptr<Factory>;
static FactoryPtr create() { return FactoryPtr(new Factory); }
static std::string save(Ptr todo) {
std::string out;
{
namespace io = boost::iostreams;
io::stream<io::back_insert_device<std::string> > os(out);
boost::archive::text_oarchive archive(os);
archive << todo;
}
return out;
}
static Ptr load(std::string const &s) {
Ptr p;
{
namespace io = boost::iostreams;
io::stream<io::array_source> is(io::array_source{ s.data(), s.size() });
boost::archive::text_iarchive archive(is);
archive >> p;
}
return std::move(p);
}
Ptr createExec(std::string command) { return BaseTodo::Ptr(new Exec(command)); }
};
}
int main() {
auto factory = Todo::Factory::create();
// ROUNDTRIP save,load
auto todo = factory->load(
factory->save(
factory->createExec("ls -al /home/ajonen")
)
);
std::cout << "Type: " << std::hex << std::showbase << todo->getInitType() << std::endl;
todo->Do();
}

Here's another take without virtuals, inheritance and dynamic allocations:
Live On Coliru
#include <boost/archive/text_iarchive.hpp>
#include <boost/archive/text_oarchive.hpp>
#include <boost/serialization/export.hpp>
#include <boost/serialization/variant.hpp>
#include <boost/iostreams/device/back_inserter.hpp>
#include <boost/iostreams/device/array.hpp>
#include <boost/iostreams/stream.hpp>
namespace Todo
{
struct None {
void Do() const {};
template <class Ar> void serialize(Ar&, unsigned) {}
};
class Exec {
public:
Exec(std::string const &command = "") : _command(command){};
void Do() const { std::cout << "foo: " << getCommand() << std::endl; };
std::string getCommand() const { return _command; };
private:
friend class boost::serialization::access;
template <class Ar> void serialize(Ar &ar, unsigned) {
ar &_command;
}
std::string _command;
};
using Todo = boost::variant<None, Exec>;
struct Factory {
static std::string save(Todo const& todo) {
std::string out;
{
namespace io = boost::iostreams;
io::stream<io::back_insert_device<std::string> > os(out);
boost::archive::text_oarchive archive(os);
archive << todo;
}
return out;
}
static Todo load(std::string const &s) {
Todo todo;
{
namespace io = boost::iostreams;
io::stream<io::array_source> is(io::array_source{ s.data(), s.size() });
boost::archive::text_iarchive archive(is);
archive >> todo;
}
return std::move(todo);
}
};
}
namespace visitors {
namespace detail {
template <typename F> struct internal_vis : boost::static_visitor<void> {
internal_vis(F& f) : _f(f) {}
template <typename... T>
void operator()(T&&... a) const { return _f(std::forward<T>(a)...); }
private:
F& _f;
};
}
template <typename F, typename V>
void apply(F const& f, V const& v) { return boost::apply_visitor(detail::internal_vis<F const>(f), v); }
template <typename F, typename V>
void apply(F const& f, V& v) { return boost::apply_visitor(detail::internal_vis<F const>(f), v); }
}
namespace Todo { namespace Actions { template <typename T>
void Do(T const& todo) {
visitors::apply([](auto const& cmd) { cmd.Do(); }, todo);
}
} }
int main() {
using namespace Todo;
Factory factory;
// ROUNDTRIP save,load
auto todo = factory.load(
factory.save(
Exec("ls -al /home/ajonen")
)
);
std::cout << "Type: " << std::hex << std::showbase << todo.which() << std::endl;
Actions::Do(todo);
}
Prints
Type: 0x1
foo: ls -al /home/ajonen

Related

compiler error with C++ template says that is not the member of struct

I'm a newer of using C++ template and I got trouble with template compiling.
I want to write a similar factory method with template but compiler error says that 'ip is not the member of _FileWriterInfo'. I was confused because it has be defined in NetWriterInfo struct but not in FileWriterInfo. And if I cancel the 'ip' member defination, compiler works. Apparently T param of NetWriter may infer to FileWriterInfo struct by mistake. How can I get rid of it? plz help.
#include <iostream>
#include <string>
enum WriterFormat
{
WTYPE_FILE = 0,
WTYPE_NET = 1
};
typedef struct _FileWriterInfo
{
std::string name;
std::string page;
}FileWriterInfo;
typedef struct _NetWriterInfo
{
std::string name;
std::string ip;
}NetWriterInfo;
template<typename T>
class Writer
{
public:
virtual ~Writer() {}
virtual std::string Write(T info) = 0;
};
template<typename T>
class FileWriter : public Writer<T>
{
public:
std::string Write(T info) override {
std::cout << "name:" << info.name << "\n";
std::cout << "page:" << info.page << "\n";
return info.name;
}
};
template<typename T>
class NetWriter : public Writer<T>
{
public:
std::string Write(T info) override {
std::cout << "name:" << info.name << "\n";
std::cout << "ip:" << info.ip << "\n";
return info.name;
}
};
class Creator
{
Creator() {};
public:
template<typename T>
static Writer<T>* CreateWriter(WriterFormat fmt)
{
Writer<T>* p = nullptr;
if (fmt == WTYPE_FILE)
p = new FileWriter<T>;
if (fmt == WTYPE_NET)
p = new NetWriter<T>;
return p;
}
};
void WriteFile()
{
FileWriterInfo info = { "Hello","100" };
Writer<FileWriterInfo>* w = Creator::CreateWriter<FileWriterInfo>(WTYPE_FILE);
w->Write(info);
return;
}
int main()
{
WriteFile();
return 0;
}
The CreateWriter function instantiates the FileWriter and NetWriter classes with the FileWriterInfo structure. Accordingly, the compiler tries to instantiate the NetWriter::Write function with the type FileWriterInfo, and we get an error.
You can place Write methods directly to FileWriterInfo and NetWriterInfo stuctures (according to the principle of data encapsulation). It also can simplify the code.

Optimization-friendly Polymorphic NVI adapter class for Runtime Polymorphism (customizable ownership)

I have a polymorphic NVI class to provide an adapter for Runtime-Polymorphism.
Type-erasure is used for loose coupling.
The non-owning implementation can be optimized out in a compile-time context:
// usage just for reference
printer referenced{"Referenced"};
const auto &polymorphicPrinter = polymorphic(referenced);
polymorphicPrinter.print(8.15);
// how to optimize this?
const auto &owningPolymorhic = polymorphic(std::make_unique<printer>("Owned");
However, I am not sure what is a simple or elegant way to allow passing the ownership and still have optimization like in the above case when it is possible.
Here is my naive implementation. I also added std::bad_function_call for invalid state.
// api_header.h
#pragma once
#include <memory>
#include <string_view>
#include <functional>
class polymorphic final
{
public:
template <typename T>
polymorphic(const T& t)
: pVTable_(getVTable(t)),
pObj_(std::addressof(t), [](const void*){})
{}
template <typename T>
polymorphic(std::unique_ptr<const T> t)
: pVTable_(getVTable(*t)),
pObj_(t.release(), [](const void* pObj){
delete static_cast<const T*>(pObj);
})
{}
template <typename T>
polymorphic(std::unique_ptr<T> t)
: polymorphic(std::unique_ptr<const T>(t.release()))
{}
polymorphic(const polymorphic&) = delete;
polymorphic& operator=(const polymorphic&) = delete;
polymorphic(polymorphic&& other) noexcept
: pVTable_(other.pVTable_),
pObj_(std::move(other.pObj_))
{
other.pVTable_ = polymorphic::getInvalidVTable();
}
polymorphic& operator=(polymorphic &&other) noexcept
{
pVTable_ = other.pVTable_;
pObj_ = std::move(other.pObj_);
other.pVTable_ = polymorphic::getInvalidVTable();
return *this;
}
template <typename T>
void print(T v) const
{
pVTable_->print(pObj_.get(), v);
}
private:
polymorphic() = default;
struct v_table {
virtual void print(const void *, double) const
{
throw std::bad_function_call();
}
virtual void print(const void *, std::string_view) const
{
throw std::bad_function_call();
}
protected:
~v_table() = default;
};
static const v_table *getInvalidVTable()
{
struct : v_table {} constexpr static invalidVTable{};
return &invalidVTable;
}
template <typename T>
static const v_table *getVTable(const T&)
{
struct : v_table {
void print(const void *pObj, double v) const override
{
static_cast<const T*>(pObj)->print(v);
}
void print(const void *pObj, std::string_view v) const override
{
static_cast<const T*>(pObj)->print(v);
}
} constexpr static vTable{};
return &vTable;
}
private:
const v_table *pVTable_ = getInvalidVTable();
// TODO: optimisation-friendly?
std::unique_ptr<const void, std::function<void(const void*)>> pObj_;
};
// main.cpp
#include "api_header.h"
#include <cstddef>
#include <string>
#include <iostream>
class printer
{
public:
template <size_t L>
explicit printer(const char (&name)[L])
: name_(name, L)
{}
void print(int v) const
{
std::cout << name_ << ": " << v << std::endl;
}
void print(double v) const
{
std::cout << name_ << ": " << v << std::endl;
}
void print(std::string_view v) const
{
std::cout << name_ << ": " << v << std::endl;
}
~printer()
{
std::cout << name_ << " destroyed\n";
}
private:
std::string name_;
};
int main()
{
printer pReferenced{"referenced"};
{
const auto &polyReferenced = polymorphic(pReferenced);
polyReferenced.print(4);
polyReferenced.print(8.15);
polyReferenced.print("oceanic");
std::cout << "non-owning polymorphic destroyed\n\n";
}
{
const auto &polyOwned = polymorphic(std::make_unique<printer>("owned"));
polyOwned.print(4);
polyOwned.print(8.15);
polyOwned.print("oceanic");
}
std::cout << "owning polymorphic destroyed\n\n";
std::cout << "Invalidating polymorphic reference\n";
try {
auto invalidReferenced = polymorphic(pReferenced);
auto polyMoved = std::move(invalidReferenced);
polyMoved.print("I have been moved");
invalidReferenced.print("This will throw");
}
catch (const std::bad_function_call &e)
{
std::cout << "error: " << e.what() << std::endl;
}
}
This code:
https://godbolt.org/z/Pc8981ns8
Is it possible to make this code optimization-friendly?

Input stream error when partially deserializing derived class into base class using Boost

When I serialize a derived class using boost and try to deserialize only the base part, I get input stream error. I guess my code is wrong. Is there a way to deserialize only the base part of a derived object using boost archive?
Reason for this code is that I am trying to implement a design to send derived objects from one process to another. The receiving process will look at the ID in the base part to decide which derived object is received.
This is the test code with which I am trying to verify that this is possible using boost, but I get input stream error on executing this
class DataIface
{
public:
DataIface()
:num(0)
{
}
DataIface( int num):
num(num)
{
}
int num;
template< class Archive >
void serialize( Archive& ar, const unsigned int version )
{
std::cout<<"Serializing base class \n"<<std::endl;
ar & num;
}
};
class Data1 : public DataIface
{
private:
friend class boost::serialization::access;
public:
Data1()
:a(0)
{
};
Data1( int a, int num):
DataIface(num),
a(a)
{
}
int a;
template< class Archive >
void serialize( Archive& ar, const unsigned int version )
{
std::cout<<"Serializing derived class \n"<<std::endl;
ar & boost::serialization::base_object<DataIface>(*this);
ar & a;
}
};
int main()
{
Data1 obj(10, 20);
std::ostringstream oss;
boost::archive::text_oarchive oa( oss );
oa << obj;
Data1 obj2;
std::istringstream iss(oss.str());
boost::archive::text_iarchive ia( iss );
ia >> obj2;
cout<< obj2.a << std::endl;
cout << obj2.num << std::endl;
DataIface iface;
try
{
ia >> iface;
}
catch(std::exception& e)
{
std::cout<<e.what()<<std::endl;
}
cout << iface.num << std::endl;
return 0;
}
Any help would be appreciated
This is the test code that I am trying to verify that this is possible using boost and I get input stream error
What is the conclusion?
The conclusion is: it doesn't work. That's because it's not a feature. No where in the documentation does it even suggest you can do this.
Runtime Polymorphism
Just use polymorphism as intended!
Live On Coliru
#include <iostream>
#include <sstream>
#include <boost/archive/text_oarchive.hpp>
#include <boost/archive/text_iarchive.hpp>
#include <boost/serialization/serialization.hpp>
#include <boost/serialization/export.hpp>
class DataIface {
public:
virtual ~DataIface() {}
template <class Archive> void serialize(Archive &ar, const unsigned int version) {
std::cout << __PRETTY_FUNCTION__ << "\n";
}
};
class Data1 : public DataIface {
friend class boost::serialization::access;
public:
Data1(int a=0) : a(a) {}
int a;
template <class Archive> void serialize(Archive &ar, const unsigned int version) {
std::cout << __PRETTY_FUNCTION__ << "\n";
ar &boost::serialization::base_object<DataIface>(*this);
ar &a;
}
};
class Data2 : public DataIface {
friend class boost::serialization::access;
public:
Data2(int b=0) : b(b) {}
int b;
template <class Archive> void serialize(Archive &ar, const unsigned int version) {
std::cout << __PRETTY_FUNCTION__ << "\n";
ar &boost::serialization::base_object<DataIface>(*this);
ar &b;
}
};
BOOST_CLASS_EXPORT(Data1)
BOOST_CLASS_EXPORT(Data2)
int main() {
DataIface* tests[] = { new Data1(10), new Data2(-10) };
for(auto testobj : tests)
{
std::ostringstream oss;
{
boost::archive::text_oarchive oa(oss);
oa << testobj;
}
{
std::istringstream iss(oss.str());
boost::archive::text_iarchive ia(iss);
DataIface* obj = nullptr;
ia >> obj;
if (Data1* obj1 = dynamic_cast<Data1*>(obj))
std::cout << "It's a Data1: " << obj1->a << "\n";
if (Data2* obj2 = dynamic_cast<Data2*>(obj))
std::cout << "It's a Data2: " << obj2->b << "\n";
}
}
for(auto ptr : tests) delete ptr;
}
Prints:
void Data1::serialize(Archive&, unsigned int) [with Archive = boost::archive::text_oarchive]
void DataIface::serialize(Archive&, unsigned int) [with Archive = boost::archive::text_oarchive]
void Data1::serialize(Archive&, unsigned int) [with Archive = boost::archive::text_iarchive]
void DataIface::serialize(Archive&, unsigned int) [with Archive = boost::archive::text_iarchive]
It's a Data1: 10
void Data2::serialize(Archive&, unsigned int) [with Archive = boost::archive::text_oarchive]
void DataIface::serialize(Archive&, unsigned int) [with Archive = boost::archive::text_oarchive]
void Data2::serialize(Archive&, unsigned int) [with Archive = boost::archive::text_iarchive]
void DataIface::serialize(Archive&, unsigned int) [with Archive = boost::archive::text_iarchive]
It's a Data2: -10
Static Polymorphism
Alternatively, use a variant. This saves you the hassle of manual dynamic allocations and the potential cost of virtual dispatch.
Live On Coliru
#include <iostream>
#include <sstream>
#include <boost/archive/text_oarchive.hpp>
#include <boost/archive/text_iarchive.hpp>
#include <boost/serialization/serialization.hpp>
#include <boost/serialization/variant.hpp>
class Data1 {
friend class boost::serialization::access;
public:
Data1(int a=0) : a(a) {}
int a;
template <class Archive> void serialize(Archive &ar, const unsigned int version) {
ar &a;
}
friend std::ostream& operator<<(std::ostream& os, Data1 const& d) {
return os << "It's a Data1: " << d.a;
}
};
class Data2 {
friend class boost::serialization::access;
public:
Data2(int b=0) : b(b) {}
int b;
template <class Archive> void serialize(Archive &ar, const unsigned int version) {
ar &b;
}
friend std::ostream& operator<<(std::ostream& os, Data2 const& d) {
return os << "It's a Data2: " << d.b;
}
};
int main() {
using V = boost::variant<Data1, Data2>;
V tests[] = { Data1{10}, Data2{-10} };
for(auto testobj : tests)
{
std::ostringstream oss;
{
boost::archive::text_oarchive oa(oss);
oa << testobj;
}
{
std::istringstream iss(oss.str());
boost::archive::text_iarchive ia(iss);
V deserialized;
ia >> deserialized;
std::cout << deserialized << "\n";
}
}
}
This prints:
It's a Data1: 10
It's a Data2: -10

Boost serialization of reference member abstract class

I'm trying to figure out how to serialize a class that I put together with Boost. I'll get right to the code:
#ifndef TEST_H_
#define TEST_H_
#include <iostream>
#include <boost/serialization/serialization.hpp>
#include <boost/archive/binary_oarchive.hpp>
#include <boost/archive/binary_iarchive.hpp>
class Parent
{
public:
int test_val = 1234234;
int p()
{
return 13294;
}
int get_test_val()
{
std::cout << test_val << std::endl;
return test_val;
}
friend class boost::serialization::access;
template<class Archive>
void serialize(Archive &ar, const unsigned int /*version*/)
{
ar &test_val;
}
};
class RefMem : public Parent
{
public:
RefMem()
{
test_val = 12342;
std::cout << test_val << std::endl;
}
};
class Test
{
public:
friend class boost::serialization::access;
int t_;
Parent &parent_;
Test(int t, Parent &&parent = RefMem());
template<class Archive>
void serialize(Archive &ar, const unsigned int file_version){
ar &t_;
ar &parent_;
}
//template<class
};
#endif
#include "test.h"
#include <iostream>
#include <sstream>
#include <boost/serialization/serialization.hpp>
#include <boost/archive/text_oarchive.hpp>
#include <boost/archive/text_iarchive.hpp>
Test :: Test(int t, Parent &&parent) : parent_(parent)
{
std::cout << this->parent_.test_val << std::endl;
t_ = t;
parent_ = parent;
}
int main()
{
Test test = Test(50);
std::cout << "t_: " << test.t_ << std::endl;
std::cout << "Test val: " << test.parent_.get_test_val() << std::endl;
std::ostringstream oss;
{
boost::archive::text_oarchive oa(oss);
oa << test;
}
Test cloned;
std::istringstream iss(oss.str());
{
boost::archive::text_iarchive ia(iss);
ia >> cloned;
}
std::cout << "t_: " << cloned.t_ << std::endl;
std::cout << "Test val: " << cloned.parent_.get_test_val() << std::endl;
}
I'm basically shooting in the dark. I'm new to C++ and I could get a basic example to work, but nothing like this where I serialize a reference member that is a child of an abstract class and then deserialize it. This code is just replicating what I'm trying to do in another program. I have a few random functions/variables just for testing.
Edit: How would I get this code to compile and work properly?
You're confused about the ownership semantics of references.
The reference parent_ merely "points" to an instance of RefMem¹. When you serialize, it's "easy" to write these (because they're lvalue-references, the value itself will have been serialized).
However for deserialization, things are not so simple, simply because we do-not have an instance of MemRef to "point" to. We could expect Boost Serialization to (somehow) dynamically instantiate a MemRef out of thin air and silently make the reference point to it. However, at best this will lead to memory leaks.
There's another thing about reference members specifically. Reference member can only be initialized in the constructor's initializer list.
Because Boost Serialization serializes values it does not construct these objects, and the question is how the reference can even be initialized at all.
Your current constructor has a number of related issues:
Test(int t, Parent && parent = RefMem()) : parent_(parent) {
std::cout << __FUNCTION__ << ":" << this->parent_.test_val << "\n";
t_ = t;
parent_ = parent; // OOPS! TODO FIXME
}
firstly, the constructor disables the compiler-generated default constructor, so that, indeed, the line Test cloned; couldn't even compile
secondly, the default argument for parent is a rvalue-reference and it becomes dangling as soon as the constructor returns. Your program has Undefined Behaviour
Thirdly the line
parent_ = parent; // OOPS! TODO FIXME
doesn't do what you think it does. It copies the value of the Parent object from parent over the object referred to by parent_. This is likely not visible as parent_ and parent are the same object here, but there's even Object Slicing involved (What is object slicing?).
What do?
Best to regroup and hit the documentation for Serialization of References:
Classes that contain reference members will generally require non-default
constructors as references can only be set when an instance is constructed.
The example of the previous section is slightly more complex if the class has
reference members. This raises the question of how and where the objects
being referred to are stored and how are they created. Also there is the
question about references to polymorphic base classes. Basically, these are
the same questions that arise regarding pointers. This is no surprise as
references are really a special kind of pointer.
We address these questions by serializing references as though they were
pointers.
(emphasis mine)
The documentation does go on to suggest load_construct_data/save_construct_data to alleviate the non-default-constructibility of Test.
Note that their suggestion to handle the reference member as a pointer seems nice, but it only makes sense if the actual pointed-to object is also serialized through a pointer in the same archive. In such case Object Tracking will spot the aliasing pointer and avoid creating a duplicate instance.
If not, you'll still have your memory leak, and possibly broken program state.
Demo Using load/save_construct_data
Here's a demo of essentially the technique outlined above. Note that we're leaking the dynamically allocated objects. I don't like this style because it's essentially treating the reference as if it were a pointer.
If that's what we want, we should consider using pointers (see below)
Live On Coliru
#ifndef TEST_H_
#define TEST_H_
#include <iostream>
#include <boost/serialization/serialization.hpp>
#include <boost/archive/binary_oarchive.hpp>
#include <boost/archive/binary_iarchive.hpp>
class Parent {
public:
int test_val = 1234234;
int p() { return 13294; }
int get_test_val() {
std::cout << __PRETTY_FUNCTION__ << ":" << test_val << "\n";
return test_val;
}
template <class Archive> void serialize(Archive &ar, unsigned) {
ar & test_val;
}
};
class RefMem : public Parent {
public:
RefMem() {
test_val = 12342;
std::cout << __PRETTY_FUNCTION__ << ":" << test_val << "\n";
}
};
class Test {
public:
friend class boost::serialization::access;
int t_;
Parent &parent_;
Test(int t, Parent& parent) : parent_(parent) {
std::cout << __PRETTY_FUNCTION__ << ":" << this->parent_.test_val << "\n";
t_ = t;
}
template <class Archive> void serialize(Archive &ar, const unsigned int file_version) {
ar &t_;
//ar &parent_; // how would this behave? We don't own it... Use pointers
}
// template<class
};
namespace boost { namespace serialization {
template<class Archive>
inline void save_construct_data(Archive & ar, const Test * t, const unsigned int file_version) {
// save data required to construct instance
ar << t->t_;
// serialize reference to Parent as a pointer
Parent* pparent = &t->parent_;
ar << pparent;
}
template<class Archive>
inline void load_construct_data(Archive & ar, Test * t, const unsigned int file_version) {
// retrieve data from archive required to construct new instance
int m;
ar >> m;
// create and load data through pointer to Parent
// tracking handles issues of duplicates.
Parent * pparent;
ar >> pparent;
// invoke inplace constructor to initialize instance of Test
::new(t)Test(m, *pparent);
}
}}
#endif
#include <iostream>
#include <sstream>
#include <boost/serialization/serialization.hpp>
#include <boost/archive/text_oarchive.hpp>
#include <boost/archive/text_iarchive.hpp>
int main() {
Parent* the_instance = new RefMem;
Test test = Test(50, *the_instance);
std::cout << "t_: " << test.t_ << "\n";
std::cout << "Test val: " << test.parent_.get_test_val() << "\n";
std::ostringstream oss;
{
boost::archive::text_oarchive oa(oss);
Test* p = &test;
oa << the_instance << p; // NOTE SERIALIZE test AS-IF A POINTER
}
{
Parent* the_cloned_instance = nullptr;
Test* cloned = nullptr;
std::istringstream iss(oss.str());
{
boost::archive::text_iarchive ia(iss);
ia >> the_cloned_instance >> cloned;
}
std::cout << "t_: " << cloned->t_ << "\n";
std::cout << "Test val: " << cloned->parent_.get_test_val() << "\n";
std::cout << "Are Parent objects aliasing: " << std::boolalpha <<
(&cloned->parent_ == the_cloned_instance) << "\n";
}
}
Prints
RefMem::RefMem():12342
Test::Test(int, Parent&):12342
t_: 50
int Parent::get_test_val():12342
Test val: 12342
Test::Test(int, Parent&):12342
t_: 50
int Parent::get_test_val():12342
Test val: 12342
Are Parent objects aliasing: true
Alternatively: say what we want
To avoid the leakiness and the usability issues associated with reference members, let's use a shared_ptr instead!
Live On Coliru
#include <iostream>
#include <boost/serialization/serialization.hpp>
#include <boost/serialization/shared_ptr.hpp>
#include <boost/archive/text_oarchive.hpp>
#include <boost/archive/text_iarchive.hpp>
#include <boost/make_shared.hpp>
class Parent {
public:
int test_val = 1234234;
int p() { return 13294; }
int get_test_val() {
std::cout << __PRETTY_FUNCTION__ << ":" << test_val << "\n";
return test_val;
}
template <class Archive> void serialize(Archive &ar, unsigned) {
ar & test_val;
}
};
class RefMem : public Parent {
public:
RefMem() {
test_val = 12342;
std::cout << __PRETTY_FUNCTION__ << ":" << test_val << "\n";
}
};
using ParentRef = boost::shared_ptr<Parent>;
class Test {
public:
int t_ = 0;
ParentRef parent_;
Test() = default;
Test(int t, ParentRef parent) : t_(t), parent_(parent) { }
template <class Archive> void serialize(Archive &ar, const unsigned int file_version) {
ar & t_ & parent_;
}
};
#include <sstream>
int main() {
ParentRef the_instance = boost::make_shared<RefMem>();
Test test = Test(50, the_instance);
std::cout << "t_: " << test.t_ << "\n";
std::cout << "Test val: " << test.parent_->get_test_val() << "\n";
std::ostringstream oss;
{
boost::archive::text_oarchive oa(oss);
oa << the_instance << test; // NOTE SERIALIZE test AS-IF A POINTER
}
{
ParentRef the_cloned_instance;
Test cloned;
std::istringstream iss(oss.str());
{
boost::archive::text_iarchive ia(iss);
ia >> the_cloned_instance >> cloned;
}
std::cout << "t_: " << cloned.t_ << "\n";
std::cout << "Test val: " << cloned.parent_->get_test_val() << "\n";
std::cout << "Are Parent objects aliasing: " << std::boolalpha <<
(cloned.parent_ == the_cloned_instance) << "\n";
}
}
Note that there is no complication anymore. No memory leaks, not even when you don't serialize the RefMem instance separately. And the object tracking works fine with shared pointers (as implemented through boost/serialization/shared_pointer.hpp).
¹ or anything else deriving from Parent, obviously

boost serialiaze input stream error

I am working on a simple serialization class. I keep throwing an exception on the input stream. I have put together the below example of what I am attempting to accomplish in simple terms.
I have this simple example of boost serialization that I am getting an exception on:
#include <boost/serialization/serialization.hpp>
#include <boost/archive/text_oarchive.hpp>
#include <boost/archive/text_iarchive.hpp>
#include <boost/serialization/export.hpp>
#define NVP(X) X
class base {
public:
friend class boost::serialization::access;
base (){ v1 = 10;}
int v1;
template<class Archive>
void serialize(Archive & ar, const unsigned int file_version)
{
ar & NVP(v1);
}
virtual void bla()=0;
};
class derived : public base {
public:
friend class boost::serialization::access;
int v2 ;
derived() { v2 = 100;}
template<class Archive>
void serialize(Archive & ar, const unsigned int file_version){
boost::serialization::base_object<base>(* this);
ar & NVP(v2);
}
virtual void bla(){};
};
BOOST_CLASS_EXPORT(base);
BOOST_CLASS_EXPORT_GUID(derived, "derived");
int main ( )
{
std::stringstream ss;
boost::archive::text_oarchive ar(ss);
base *b = new derived();
ar << NVP(b);
std::cout << ss.str()<<std::endl;
std::istringstream ssi;
base *b1 = new derived();
{
boost::archive::text_iarchive ar1(ssi);
ar1 >> b1;
}
//std::cout << ssi.str();
std::cout << "v1: " << b1->v1 << std::endl;
}
The exception that I am getting is:
terminate called after throwing an instance of 'boost::archive::archive_exception'
what(): input stream error
Any help would be appreciated.
You're reading from an empty stream:
std::istringstream ssi;
// ...
boost::archive::text_iarchive ar1(ssi);
Also, you leak this object:
base *b1 = new derived();
Here's a fixed example, notes:
it's very good practice/important to close archives before using the streamed data
BOOST_CLASS_EXPORT_GUID(derived, "derived") doesn't add anything beyond BOOST_CLASS_EXPORT(derived)
you can print the v2 conditionally:
if (auto* d = dynamic_cast<derived*>(b1))
std::cout << "v2: " << d->v2 << std::endl;
I've used bla() as an example to print the values instead
NVP() is a bit iffy there. Why not just leave it out for non-tagged archives (ie. other than XML)? If you intend to support XML, just use BOOST_SERIALIZATION_NVP, boost::serialization::make_nvp etc.
std::cout << "v2: " << b1->v2 << std::endl; was completely out of place
just initialize b1 to null so you don't leak it; remember to free all pointers (use smart pointers!)
the mix of public: and friend in your types didn't really mean much
Live On Coliru
#include <boost/serialization/serialization.hpp>
#include <boost/archive/text_oarchive.hpp>
#include <boost/archive/text_iarchive.hpp>
#include <boost/serialization/export.hpp>
#include <sstream>
class base {
public:
base(int v1) : v1(v1) {}
virtual void bla() const = 0;
private:
friend class boost::serialization::access;
template <class Archive> void serialize(Archive &ar, unsigned /*int const file_version*/) {
ar & BOOST_SERIALIZATION_NVP(v1);
}
protected:
int v1;
};
class derived : public base {
public:
derived(int v1 = 10, int v2 = 100) : base(v1), v2(v2) {}
virtual void bla() const {
std::cout << "v1: " << v1 << ", v2: " << v2 << "\n";
}
private:
friend class boost::serialization::access;
int v2;
template <class Archive> void serialize(Archive &ar, unsigned /*int const file_version*/) {
boost::serialization::base_object<base>(*this);
ar & BOOST_SERIALIZATION_NVP(v2);
}
};
BOOST_CLASS_EXPORT(base)
BOOST_CLASS_EXPORT(derived)
int main() {
std::stringstream ss;
{
boost::archive::text_oarchive ar(ss);
base *b = new derived();
ar << boost::serialization::make_nvp("base", b);
delete b; // TODO use RAII instead
}
std::cout << ss.str() << std::endl;
base *deserialized = nullptr;
{
boost::archive::text_iarchive ar1(ss);
ar1 >> boost::serialization::make_nvp("base", deserialized);
}
deserialized->bla();
delete deserialized;
}
Prints
22 serialization::archive 12 0 7 derived 1 0
0 100
v1: 10, v2: 100