How to serialize/deserialize derived class inheriting base class without default constructor?
Please offer serializing boost functions for the following classes
struct Base
{
Base(int b) : b(b) {}
const int b;
}
struct Derived : public Base
{
Derived(float d, int b) : Base(b), d(d) {}
const float d;
}
Your use-case straddles two of the "special considerations" documented by Boost Serialization:
Non-default constructors
Pointers to objects of derived classes
Note that I'm going to assume you want dynamic polymorphism, and to get this you need at least a virtual destructor. If you don't you will end up with Undefined Behaviour.
Combining the two for your example:
Live On Coliru
#include <boost/archive/text_oarchive.hpp>
#include <boost/archive/text_iarchive.hpp>
#include <boost/serialization/base_object.hpp>
#include <boost/serialization/serialization.hpp>
#include <boost/serialization/export.hpp>
#include <iostream>
struct Base {
Base(int b) : b(b) {}
virtual ~Base() = default;
const int b;
};
namespace boost::serialization {
template <typename Ar> inline void serialize(Ar&, Base&, unsigned) {}
template <typename Ar>
inline void save_construct_data(Ar& ar, Base const* p, unsigned) {
// save data required to construct instance
ar << p->b;
}
template <typename Ar>
inline void load_construct_data(Ar& ar, Base* p, unsigned) {
int attribute;
ar >> attribute;
// invoke inplace constructor to initialize instance
::new (p) Base(attribute);
}
} // namespace boost::serialization
struct Derived : public Base {
Derived(float d, int b) : Base(b), d(d) {}
const float d;
};
BOOST_CLASS_EXPORT(Base)
BOOST_CLASS_EXPORT(Derived)
namespace boost::serialization {
template <typename Ar> inline void serialize(Ar& ar, Derived& d, unsigned) {
ar & boost::serialization::base_object<Base>(d);
}
template <typename Ar>
inline void save_construct_data(Ar& ar, Derived const* p, unsigned) {
// save data required to construct instance
ar & p->b & p->d;
}
template <typename Ar>
inline void load_construct_data(Ar& ar, Derived* p, unsigned) {
int b;
float d;
ar & b & d;
// invoke inplace constructor to initialize instance
::new (p) Derived(d, b);
}
} // namespace boost::serialization
std::string save(Base* b) {
std::ostringstream oss;
{
boost::archive::text_oarchive oa(oss);
oa << b;
}
return oss.str();
}
Base* load(std::string txt) {
std::istringstream iss(std::move(txt));
boost::archive::text_iarchive ia(iss);
Base* b = nullptr;
ia >> b;
return b;
}
int main() {
for (Base* object :
{
new Base(-99),
static_cast<Base*>(new Derived(3.14, 42)),
}) //
{
std::cout << "----\n";
Base* roundtrip = load(save(object));
delete object;
std::cout << "roundtrip: b=" << roundtrip->b;
if (auto* as_derived = dynamic_cast<Derived const*>(roundtrip)) {
std::cout << ", d=" << as_derived->d;
}
std::cout << "\n";
delete roundtrip;
}
}
Prints
----
roundtrip: b=-99
----
roundtrip: b=42, d=3.14
SAFETY FIRST
Of course, don't use raw new/delete:
using unique_ptr Live On Coliru
using shared_ptr (note the dynamic_pointer_cast) Live On Coliru
The lack of a default constructor is the least of the problems here.
This class cannot be deserialized, as is. Once an instance of this class is constructed its const class members will veto any further attempts to deserialize anything.
In this particular case your only option is to deserialize a lonely int and a float by themself. Then use the deserialized int and float to construct the class.
Related
Currently we use POD which is stored in nested structs. Example:
#define MaxNum1 100;
#define MaxNum2 50;
struct A
{
int Value[MaxNum1];
char SomeChar = 'a';
};
struct B
{
A data[MaxNum2];
float SomeFloat = 0.1f;
};
int main()
{
B StructBObject = {};
}
We want to enhance our data structures using std::vector just like this:
struct NewA
{
std::vector<int> Value;
char SomeChar = 'a';
};
struct NewB
{
std::vector<NewA> data;
float SomeFloat = 0.1f;
};
int main()
{
NewB StructNewBObject = {};
}
The only argument against this modification is that NewA and NewB are no POD anymore and this makes reading/writing to a file more complicated.
How is it possible to read/write NewA and NewB to a file using boost::serialization with minimal
code changes to NewA and NewB? Minimal code changes are important because we use for example big structs which have up to 7 nested levels.
You can serialize using boost serialization¹:
template <typename Ar> void serialize(Ar& ar, A& a, unsigned) {
ar & a.Value & a.SomeChar;
}
template <typename Ar> void serialize(Ar& ar, B& b, unsigned) {
ar & b.data & b.SomeFloat;
}
Using these, you will already have the correct behaviour out of the box with both the C-array and std::vector approaches.
If you want to keep using fixed-size trivially-copyable types², you can use something like Boost Container's static_vector: it will keep track of the current size, but the data is statically allocated right inside the structures.
TRIPLE DEMO
Here's a triple demo program with three implementations depending on the IMPL variable.
As you can see the bulk of the code is kept invariant. However, for "best comparison" I've made sure that all the containers are at half capacity (50/25) before serialization.
The main program also deserializes.
Live On Coliru
#include <boost/iostreams/device/back_inserter.hpp>
#include <boost/iostreams/device/array.hpp>
#include <boost/iostreams/stream.hpp>
#include <boost/archive/binary_oarchive.hpp>
#include <boost/archive/binary_iarchive.hpp>
#include <boost/serialization/access.hpp>
#include <boost/serialization/is_bitwise_serializable.hpp>
#include <boost/serialization/binary_object.hpp>
#include <iostream>
#if (IMPL==0) // C arrays
struct A {
int Value[100];
char SomeChar = 'a';
};
struct B {
A data[50];
float SomeFloat = 0.1f;
};
template <typename Ar> void serialize(Ar& ar, A& a, unsigned) {
ar & a.Value & a.SomeChar;
}
template <typename Ar> void serialize(Ar& ar, B& b, unsigned) {
ar & b.data & b.SomeFloat;
}
#elif (IMPL==1) // std::vector
#include <boost/serialization/vector.hpp>
struct A {
std::vector<int> Value;
char SomeChar = 'a';
};
struct B {
std::vector<A> data;
float SomeFloat = 0.1f;
};
template <typename Ar> void serialize(Ar& ar, A& a, unsigned) {
ar & a.Value & a.SomeChar;
}
template <typename Ar> void serialize(Ar& ar, B& b, unsigned) {
ar & b.data & b.SomeFloat;
}
#elif (IMPL==2) // static_vector
#include <boost/serialization/vector.hpp>
#include <boost/container/static_vector.hpp>
struct A {
boost::container::static_vector<int, 100> Value;
char SomeChar = 'a';
};
struct B {
boost::container::static_vector<A, 50> data;
float SomeFloat = 0.1f;
};
template <typename Ar> void serialize(Ar& ar, A& a, unsigned) {
ar & boost::serialization::make_array(a.Value.data(), a.Value.size()) & a.SomeChar;
}
template <typename Ar> void serialize(Ar& ar, B& b, unsigned) {
ar & boost::serialization::make_array(b.data.data(), b.data.size()) & b.SomeFloat;
}
#endif
namespace bio = boost::iostreams;
static constexpr auto flags = boost::archive::archive_flags::no_header;
using BinaryData = std::vector</*unsigned*/ char>;
int main() {
char const* impls[] = {"C style arrays", "std::vector", "static_vector"};
std::cout << "Using " << impls[IMPL] << " implementation: ";
BinaryData serialized_data;
{
B object = {};
#if IMPL>0
{
// makes sure all containers half-full
A element;
element.Value.resize(50);
object.data.assign(25, element);
}
#endif
bio::stream<bio::back_insert_device<BinaryData>> os { serialized_data };
boost::archive::binary_oarchive oa(os, flags);
oa << object;
}
std::cout << "Size: " << serialized_data.size() << "\n";
{
bio::array_source as { serialized_data.data(), serialized_data.size() };
bio::stream<bio::array_source> os { as };
boost::archive::binary_iarchive ia(os, flags);
B object;
ia >> object;
}
}
Printing
Using C style arrays implementation: Size: 20472
Using std::vector implementation: Size: 5256
Using static_vector implementation: Size: 5039
Final Thoughts
See also:
Boost serialization bitwise serializability
https://www.boost.org/doc/libs/1_72_0/libs/serialization/doc/wrappers.html#binaryobjects
¹ (but keep in mind portability, as you probably already are aware with the POD approach, see C++ Boost::serialization : How do I archive an object in one program and restore it in another?)
² not POD, as with the NSMI your types weren't POD
I have seen many questions, tutorials, and documentation involving serializing derived classes, and I haven't been able to reach a consensus on several issues, including (and illustrated in the following code):
boost::serialization::base_object vs BOOST_SERIALIZATION_BASE_OBJECT_NVP
archive & mData; vs archive & BOOST_SERIALIZATION_NVP(mData);
The usefulness of BOOST_SERIALIZATION_ASSUME_ABSTRACT(AbstractPoint);
Requiring serialize() for a class in the hierarchy that doesn't need to serialize anything.
Code:
#include <boost/archive/text_oarchive.hpp>
#include <boost/archive/text_iarchive.hpp>
#include <boost/serialization/shared_ptr.hpp>
#include <boost/serialization/base_object.hpp>
#include <fstream>
class AbstractPoint
{
public:
virtual ~AbstractPoint(){}
virtual void DoSomething() = 0;
// Even though the class is abstract, we still need this
template<class TArchive>
void serialize(TArchive& archive, const unsigned int version)
{
// do nothing
}
};
// This doesn't seem to do anything
//BOOST_SERIALIZATION_ASSUME_ABSTRACT(AbstractPoint);
class Point : public AbstractPoint
{
public:
Point() = default;
Point(const double data) : mData(data) {}
void DoSomething(){}
template<class TArchive>
void serialize(TArchive& archive, const unsigned int version)
{
// These two seem equivalent. Without one of them, unregistered void cast
archive & boost::serialization::base_object<AbstractPoint>(*this);
//archive & BOOST_SERIALIZATION_BASE_OBJECT_NVP(AbstractPoint);
// These two seem equivalent
archive & mData;
//archive & BOOST_SERIALIZATION_NVP(mData);
}
double mData;
};
int main()
{
std::shared_ptr<AbstractPoint> point(new Point(7.4));
std::ofstream outputStream("test.txt");
boost::archive::text_oarchive outputArchive(outputStream);
outputArchive.register_type<Point>();
outputArchive << point;
outputStream.close();
std::shared_ptr<AbstractPoint> pointRead;
std::ifstream inputStream("test.txt");
boost::archive::text_iarchive inputArchive(inputStream);
inputArchive.register_type<Point>();
inputArchive >> pointRead;
std::shared_ptr<Point> castedPoint = std::dynamic_pointer_cast<Point>(pointRead);
std::cout << castedPoint->mData << std::endl;
return 0;
}
The other major issue is where to register classes in a "real" environment (when there is linking, etc.), but that seems worth a separate question.
It would be great to have a "gold standard" example of these kinds of things in the documentation, but at the least on StackOverflow :)
boost::serialization::base_object vs BOOST_SERIALIZATION_BASE_OBJECT_NVP
The NVP wrapper is only ever required for archives that have element naming, like XML.
Unless you use it, base_object<> is cleaner and simpler.
archive & mData; vs archive & BOOST_SERIALIZATION_NVP(mData);
Ditto
The usefulness of BOOST_SERIALIZATION_ASSUME_ABSTRACT(AbstractPoint);
I assume it will merely be an optimization - suppressing registered type information with each archive type, since you told the framework it will never be de-serializing instances of the type
Requiring serialize() for a class in the hierarchy that doesn't need to serialize anything.
You don't need it, unless you need the type information about a polymorphic base there. When do you need that? When you need to de-serialize pointers of the base type.
Hence, if you have
struct A{ virtual ~A(); };
struct B:A{};
struct C:B{};
struct D:B{};`
you will need serialization for A (but not B) if you (de)serialize A*. You will need serialization for B if you (de)serialize B*.
Similarly, if your type is not polymorphic (virtual) or you don't use it as such, you don't need any base serialization (e.g. if you (de)serialize C or D directly).
Finally, if you have struct A{}; struct B:A{}; there is no need to tell Boost Serialization about the base type at all, (you could just do the serialization from within B).
Update in response to your samples:
case1.cpp looks ok
case2.cpp needs to call base serialization, of course; not necessarily using base_object because you require polymorphic serialization:
template<class TArchive> void serialize(TArchive& archive, unsigned) {
archive & boost::serialization::base_object<AbstractPoint>(*this)
& mData;
// OR:
archive & static_cast<AbstractPoint&>(*this)
& mData;
// OR even just:
archive & mParentData
& mData;
}
case3.cpp: indeed, it's exactly like case1, but with dynamic allocation and object tracking
case4.cpp: is exactly like case1, but with dynamic allocation and object tracking; NB!! it requires explicitly serializing for the base!
template<class TArchive> void serialize(TArchive& archive, unsigned) {
archive & boost::serialization::base_object<AbstractPoint>(*this)
& mData;
}
case5.cpp: yes, but it's more typical to use the CLASS_EXPORT* macros from boost/serialization/export.hpp
Bitrot insurance:
case1.cpp
case2.cpp
case3.cpp
case4.cpp
case5.cpp
Following #sehe's advice, here are some example uses:
Serialize derived class object, not forwarding to parent
#include <boost/archive/text_oarchive.hpp>
#include <boost/archive/text_iarchive.hpp>
#include <fstream>
class AbstractPoint
{
public:
virtual ~AbstractPoint(){}
virtual void DoSomething() = 0;
};
class Point : public AbstractPoint
{
public:
Point() = default;
Point(const double data) : mData(data) {}
void DoSomething(){}
template<class TArchive>
void serialize(TArchive& archive, const unsigned int version)
{
archive & mData;
}
double mData;
};
int main()
{
Point point(7.4);
std::ofstream outputStream("test.txt");
boost::archive::text_oarchive outputArchive(outputStream);
outputArchive << point;
outputStream.close();
Point pointRead;
std::ifstream inputStream("test.txt");
boost::archive::text_iarchive inputArchive(inputStream);
inputArchive >> pointRead;
std::cout << pointRead.mData << std::endl;
return 0;
}
Serialize derived class object, including (automatic) forwarding to parent:
#include <boost/archive/text_oarchive.hpp>
#include <boost/archive/text_iarchive.hpp>
#include <fstream>
class AbstractPoint
{
public:
virtual ~AbstractPoint(){}
virtual void DoSomething() = 0;
double mParentData = 3.1;
template<class TArchive>
void serialize(TArchive& archive, const unsigned int version)
{
archive & mParentData;
}
};
class Point : public AbstractPoint
{
public:
Point() = default;
Point(const double data) : mData(data) {}
void DoSomething(){}
template<class TArchive>
void serialize(TArchive& archive, const unsigned int version)
{
// this is not required, the parent serialize() seems to be called automatically
// archive & boost::serialization::base_object<AbstractPoint>(*this);
archive & mData;
}
double mData;
};
int main()
{
Point point(7.4);
std::ofstream outputStream("test.txt");
boost::archive::text_oarchive outputArchive(outputStream);
outputArchive << point;
outputStream.close();
Point pointRead;
std::ifstream inputStream("test.txt");
boost::archive::text_iarchive inputArchive(inputStream);
inputArchive >> pointRead;
std::cout << pointRead.mParentData << std::endl;
std::cout << pointRead.mData << std::endl;
return 0;
}
Serialize derived class pointer, not forwarding to parent
(note nothing changes from the object case)
#include <boost/archive/text_oarchive.hpp>
#include <boost/archive/text_iarchive.hpp>
#include <boost/serialization/shared_ptr.hpp>
#include <fstream>
class AbstractPoint
{
public:
virtual ~AbstractPoint(){}
virtual void DoSomething() = 0;
};
class Point : public AbstractPoint
{
public:
Point() = default;
Point(const double data) : mData(data) {}
void DoSomething(){}
template<class TArchive>
void serialize(TArchive& archive, const unsigned int version)
{
archive & mData;
}
double mData;
};
int main()
{
std::shared_ptr<Point> point(new Point(7.4));
std::ofstream outputStream("test.txt");
boost::archive::text_oarchive outputArchive(outputStream);
outputArchive << point;
outputStream.close();
std::shared_ptr<Point> pointRead;
std::ifstream inputStream("test.txt");
boost::archive::text_iarchive inputArchive(inputStream);
inputArchive >> pointRead;
std::cout << pointRead->mData << std::endl;
return 0;
}
Serialize derived class pointer, forwarding to parent
(note nothing changes from the object case)
#include <boost/archive/text_oarchive.hpp>
#include <boost/archive/text_iarchive.hpp>
#include <boost/serialization/shared_ptr.hpp>
#include <fstream>
class AbstractPoint
{
public:
virtual ~AbstractPoint(){}
virtual void DoSomething() = 0;
template<class TArchive>
void serialize(TArchive& archive, const unsigned int version)
{
archive & mParentData;
}
double mParentData = 3.1;
};
class Point : public AbstractPoint
{
public:
Point() = default;
Point(const double data) : mData(data) {}
void DoSomething(){}
template<class TArchive>
void serialize(TArchive& archive, const unsigned int version)
{
archive & mData;
}
double mData;
};
int main()
{
std::shared_ptr<Point> point(new Point(7.4));
std::ofstream outputStream("test.txt");
boost::archive::text_oarchive outputArchive(outputStream);
outputArchive << point;
outputStream.close();
std::shared_ptr<Point> pointRead;
std::ifstream inputStream("test.txt");
boost::archive::text_iarchive inputArchive(inputStream);
inputArchive >> pointRead;
std::cout << pointRead->mParentData << std::endl;
std::cout << pointRead->mData << std::endl;
return 0;
}
Serialize base class pointer
(We now have to register the type of the derived class with the archives, as well as use boost::serialization::base_object)
#include <boost/archive/text_oarchive.hpp>
#include <boost/archive/text_iarchive.hpp>
#include <boost/serialization/shared_ptr.hpp>
#include <boost/serialization/base_object.hpp>
#include <fstream>
class AbstractPoint
{
public:
virtual ~AbstractPoint(){}
virtual void DoSomething() = 0;
// This is required if we want to serialize an AbstractPoint pointer
template<class TArchive>
void serialize(TArchive& archive, const unsigned int version)
{
// do nothing
}
};
class Point : public AbstractPoint
{
public:
Point() = default;
Point(const double data) : mData(data) {}
void DoSomething(){}
template<class TArchive>
void serialize(TArchive& archive, const unsigned int version)
{
// Without this, we get unregistered void cast
archive & boost::serialization::base_object<AbstractPoint>(*this);
archive & mData;
}
double mData;
};
int main()
{
std::shared_ptr<AbstractPoint> point(new Point(7.4));
std::ofstream outputStream("test.txt");
boost::archive::text_oarchive outputArchive(outputStream);
outputArchive.register_type<Point>();
outputArchive << point;
outputStream.close();
std::shared_ptr<AbstractPoint> pointRead;
std::ifstream inputStream("test.txt");
boost::archive::text_iarchive inputArchive(inputStream);
inputArchive.register_type<Point>();
inputArchive >> pointRead;
std::shared_ptr<Point> castedPoint = std::dynamic_pointer_cast<Point>(pointRead);
std::cout << castedPoint->mData << std::endl;
return 0;
}
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
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
I am attempting to make derived class serializable by boost serialization using pointer to base class.
Base::serialization called. Derived::serialization not called.
What did I do wrong?
#include <sstream>
#include <boost/archive/binary_oarchive.hpp>
#include <boost/serialization/serialization.hpp>
#include <boost/serialization/assume_abstract.hpp>
struct Base
{
int x;
Base() { x = 0; }
virtual ~Base() {}
template<class Archive>
void serialize(Archive &ar, const unsigned int version)
{
ar & x;
}
};
BOOST_SERIALIZATION_ASSUME_ABSTRACT(Base)
struct Derived : Base
{
int y;
Derived() { x = 1; y = 2; }
virtual ~Derived() {}
template<class Archive>
void serialize(Archive & ar, const unsigned int version)
{
ar & boost::serialization::base_object<Base>(*this);
ar & y;
}
};
int main()
{
Derived derived;
Base *basePtr = &derived;
std::string s;
std::stringstream ss(s);
boost::archive::binary_oarchive oa(ss);
oa << *basePtr;
}
Firstly, you're not serializing through a pointer, fix it:
oa << basePtr;
Secondly, you need to register derived types:
oa.register_type<Derived>();
Or, inside the serialize function:
ar.template register_type<Derived>();
You might_ also want to look at registering class information for serialization: http://www.boost.org/doc/libs/1_57_0/libs/serialization/doc/special.html#export
UPDATE To the comment/edited question:
Your question code serializes a Base* and deserializes a Derived. The types are unrelated, this could never work. They need to be identical.
Also, register the Derived type reading the input archive.
Live On Coliru
#include <sstream>
#include <boost/archive/binary_oarchive.hpp>
#include <boost/archive/binary_iarchive.hpp>
#include <boost/serialization/serialization.hpp>
#include <boost/serialization/assume_abstract.hpp>
struct Derived;
struct Base
{
int x;
Base() { x = 0; }
virtual ~Base() {}
template<class Archive>
void serialize(Archive &ar, const unsigned int version)
{
ar.template register_type<Derived>();
ar & x;
}
};
BOOST_SERIALIZATION_ASSUME_ABSTRACT(Base)
struct Derived : Base
{
int y;
Derived() { x = 1; y = 2; }
virtual ~Derived() {}
template<class Archive>
void serialize(Archive & ar, const unsigned int version)
{
ar & boost::serialization::base_object<Base>(*this);
ar & y;
}
};
int main()
{
std::stringstream ss;
{
Derived derived;
Base *basePtr = &derived;
boost::archive::binary_oarchive oa(ss);
oa.register_type<Derived>();
oa << basePtr;
ss.flush();
}
{
boost::archive::binary_iarchive ia(ss);
ia.register_type<Derived>();
Base *basePtr = nullptr;
ia >> basePtr;
std::cout << "basePtr->x = " << basePtr->x << "\n";
if (Derived* derivedPtr = dynamic_cast<Derived*>(basePtr))
std::cout << "derivedPtr->y = " << derivedPtr->y << "\n";
}
}