boost::serialization - Serializing a class derived from a generic attribute / feature container - c++

I've been struggling the past few weeks to solve an issue I am having with serializing some data using boost::serialization.
I am trying to implement a tabbed editor that utilizes a client/server architecture for a project I am working on. The current design works like this:
DerivedTab extends from a base Tab class. (For this example question I have chosen not to include examples of the DerivedTab classes.).
Tab class extends AttributeContainer class which contains a map of string attribute names to AttributeBase*.
Attribute is a templated class that extends AttributeBase. It is intended to be used as a generic data type that can hold the value any concrete data type.
And finally, AttributeBase derives from NetworkSerializable which is a pure abstract base class used as a base object type to identify the contract that objects that can be serialized over a network connection must follow.
All of this is being compiled into a library which is then statically linked into my main application. As you can see, there is quite a bit of indirection going on and quite a few pitfalls that I'm trying to work around with serializing with boost::serialization. I've stripped out all additional code not pertinent to getting these classes to serialize. However, the example code is still quite lengthy.
main.cpp:
#include <sstream>
#include <boost/archive/text_iarchive.hpp>
#include <boost/archive/text_oarchive.hpp>
#include "Tab.h"
using namespace test;
int main(int argc, char **argv)
{
std::ostringstream oarchiveStream;
boost::archive::text_oarchive outputArchive(oarchiveStream);
Tab* tab = new Tab("temp");
bool tempBool = true;
tab->RegisterAttribute("tempBool", "a temp boolean", &tempBool);
std::string tempString("1234");
tab->RegisterAttribute("tempString", "a temp string", &tempString);
outputArchive << tab;
}
Tab.h:
#ifndef __TAB_H__
#define __TAB_H__
#include "AttributeContainer.h"
#include <boost/serialization/base_object.hpp>
namespace test
{
class Tab : public AttributeContainer
{
friend class boost::serialization::access;
public:
Tab(const std::string tabName);
virtual ~Tab();
protected:
Tab();
template<class archive>
inline void serialize_attributes(archive& ar, const unsigned int version)
{
ar & BOOST_SERIALIZATION_BASE_OBJECT_NVP(AttributeContainer);
ar & boost::serialization::make_nvp("TabName", _tabName);
}
virtual void serialize(boost::archive::text_oarchive& oa, const unsigned int version);
virtual void serialize(boost::archive::text_iarchive& ia, const unsigned int version);
private:
std::string _tabName;
};
} // namespace test
BOOST_CLASS_EXPORT_KEY(test::Tab);
#endif // #ifndef __TAB_H__
Tab.cpp:
#include "Tab.h"
BOOST_CLASS_EXPORT_IMPLEMENT(test::Tab);
using namespace test;
Tab::Tab(const std::string tabName) : _tabName(tabName)
{
}
Tab::~Tab()
{
}
Tab::Tab() : _tabName("")
{
}
void Tab::serialize(boost::archive::text_oarchive& oa, const unsigned int version)
{
std::cout << "Tab::serialize" << std::endl;
serialize_attributes(oa, version);
}
void Tab::serialize(boost::archive::text_iarchive& ia, const unsigned int version)
{
serialize_attributes(ia, version);
}
AttributeContainer.h:
#ifndef __ATTRIBUTE_CONTAINER_H__
#define __ATTRIBUTE_CONTAINER_H__
#include "NetworkSerializable.h"
#include <boost/serialization/map.hpp>
#include "Attribute.h"
namespace test
{
class AttributeContainer : public NetworkSerializable
{
friend class boost::serialization::access;
public:
std::map<std::string, AttributeBase*> _attributes;
AttributeContainer() {};
virtual ~AttributeContainer() {};
template <typename _T>
void RegisterAttribute(const std::string& name, const std::string& description, _T* var)
{
std::map<std::string, AttributeBase*>::const_iterator pos;
if ( (pos = _attributes.find(name)) == _attributes.end() )
{
Attribute<_T>* attribute = new Attribute<_T>(name, description, var);
_attributes.insert(std::map<std::string, AttributeBase*>::value_type(name, attribute));
}
};
template <class archive>
inline void serialize_attributes(archive& ar, const unsigned int version)
{
ar & _attributes;
};
virtual void serialize(boost::archive::text_oarchive& oa, const unsigned int version);
virtual void serialize(boost::archive::text_iarchive& ia, const unsigned int version);
}; // end class AtributeContainer
} // end namespace test
BOOST_CLASS_EXPORT_KEY(test::AttributeContainer);
#endif // #ifndef __ATTRIBUTE_CONTAINER_H__
AttributeContainer.cpp:
#include "AttributeContainer.h"
BOOST_CLASS_EXPORT_IMPLEMENT(test::AttributeContainer);
using namespace test;
void AttributeContainer::serialize(boost::archive::text_oarchive& oa, const unsigned int version)
{
std::cout << "AttributeContainer::serialize" << std::endl;
serialize_attributes(oa, version);
}
void AttributeContainer::serialize(boost::archive::text_iarchive& ia, const unsigned int version)
{
serialize_attributes(ia, version);
}
Attribute.h:
#ifndef __ATTRIBUTE_H__
#define __ATTRIBUTE_H__
#include "AttributeBase.h"
namespace test
{
template <typename _T>
class Attribute : public AttributeBase
{
friend class AttributeContainer;
friend class boost::serialization::access;
public:
typedef _T AttributeType;
Attribute() : _data(0) {}
Attribute(const std::string& name, const std::string& description, AttributeType* var) : _data(var)
{
_name = name;
_description = description;
}
virtual ~Attribute() {}
protected:
AttributeType* _data;
template <class archive>
inline void serialize_base(archive& ar, const unsigned int version)
{
ar & BOOST_SERIALIZATION_BASE_OBJECT_NVP(AttributeBase);
ar & boost::serialization::make_nvp("Value", *_data);
}
virtual void serialize(boost::archive::text_oarchive& oa, const unsigned int version)
{
std::cout << "Attribute::serialize" << std::endl;
serialize_base(oa, version);
}
virtual void serialize(boost::archive::text_iarchive& ia, const unsigned int version)
{
serialize_base(ia, version);
}
};
} // namespace test
BOOST_CLASS_EXPORT_KEY(test::Attribute<bool>);
BOOST_CLASS_EXPORT_KEY(test::Attribute<std::string>);
#endif // #ifndef __ATRIBUTE_H__
Attribute.cpp:
#include "Attribute.h"
BOOST_CLASS_EXPORT_IMPLEMENT(test::Attribute<bool>);
BOOST_CLASS_EXPORT_IMPLEMENT(test::Attribute<std::string>);
using namespace test;
AttributeBase.h:
#ifndef __ATTRIBUTE_BASE_H__
#define __ATTRIBUTE_BASE_H__
#include "NetworkSerializable.h"
#include <string>
namespace test
{
class AttributeBase : public NetworkSerializable
{
friend class AttributeContainer;
friend class boost::serialization::access;
public:
AttributeBase();
virtual ~AttributeBase();
protected:
AttributeBase& operator=(const AttributeBase&);
AttributeBase(const AttributeBase&);
protected:
std::string _name;
std::string _description;
template<class archive>
inline void serialize_attributes(archive& ar, const unsigned int version)
{
ar & boost::serialization::make_nvp("Name", _name);
ar & boost::serialization::make_nvp("Description", _description);
}
virtual void serialize(boost::archive::text_oarchive& oa, const unsigned int version);
virtual void serialize(boost::archive::text_iarchive& ia, const unsigned int version);
}; // end class AttributeBase
} // end namespace test
BOOST_SERIALIZATION_ASSUME_ABSTRACT(test::AttributeBase);
BOOST_CLASS_EXPORT_KEY(test::AttributeBase);
#endif // #ifndef __ATTRIBUTE_BASE_H__
NetworkSerializable.h:
#ifndef __NETWORK_SERIALIZABLE_H__
#define __NETWORK_SERIALIZABLE_H__
#pragma warning(disable:4244)
#include <boost/shared_ptr.hpp>
#include <boost/serialization/nvp.hpp>
#include <boost/serialization/export.hpp>
#include <boost/archive/text_oarchive.hpp>
#include <boost/archive/text_iarchive.hpp>
namespace test
{
class NetworkSerializable
{
friend class boost::serialization::access;
public:
typedef std::shared_ptr<NetworkSerializable> NetworkSerializablePtr;
NetworkSerializable() {};
protected:
virtual void serialize(boost::archive::text_oarchive& oa, const unsigned int version) = 0;
virtual void serialize(boost::archive::text_iarchive& ia, const unsigned int version) = 0;
};
BOOST_SERIALIZATION_ASSUME_ABSTRACT(NetworkSerializable);
} // namespace test
#endif // #ifndef __NETWORK_SERIALIZABLE_H__
I've tried to make the code as concise and minimal as possible to fully demonstrate the issue I am having.
The output from the provided code is:
Tab::serialize
Tab::serialize
When the output should be:
Tab::serialize
AttributeContainer::serialize
Attribute::serialize
AttributeBase::serialize
Attribute::serialize
AttributeBase::serialize
There is quite a bit of code here for someone to digest so I would be very thankful if anyone can offer any insight where I might have strayed along the boost serialization path.

In a nutshell: your serialize member functions should not be virtual. Making them virtual results in the call to static_cast<AttributeContainer*>(this)->serialize(...), made by boost::serialization::base_object inside of Tab::serialize_attributes, landing back in Tab::serialize through virtual function dispatch.
Here's a working single-file example based on your code:
namespace serial_test
{
using namespace std;
class NetworkSerializable {
friend class boost::serialization::access;
public:
typedef std::shared_ptr<NetworkSerializable> NetworkSerializablePtr;
NetworkSerializable() {};
protected:
// void serialize(boost::archive::text_oarchive& oa, const unsigned int version) = 0;
// void serialize(boost::archive::text_iarchive& ia, const unsigned int version) = 0;
};
BOOST_SERIALIZATION_ASSUME_ABSTRACT(NetworkSerializable);
class AttributeBase : public NetworkSerializable {
friend class AttributeContainer;
friend class boost::serialization::access;
public:
AttributeBase() {}
virtual ~AttributeBase() {}
protected:
std::string _name;
std::string _description;
template<class archive>
inline void serialize_attributes(archive& ar, const unsigned int version) {
ar & boost::serialization::make_nvp("Name", _name);
ar & boost::serialization::make_nvp("Description", _description);
}
void serialize(boost::archive::text_oarchive& oa, const unsigned int version) {
cout << "AttributeBase::serialize" << endl;
serialize_attributes(oa, version);
}
void serialize(boost::archive::text_iarchive& ia, const unsigned int version) {
serialize_attributes(ia, version);
}
}; // end class AttributeBase
template <typename _T>
class Attribute : public AttributeBase {
friend class AttributeContainer;
friend class boost::serialization::access;
public:
typedef _T AttributeType;
Attribute() : _data(0) {}
Attribute(const std::string& name, const std::string& description, AttributeType* var) : _data(var) {
_name = name;
_description = description;
}
virtual ~Attribute() {}
protected:
AttributeType* _data;
template <class archive>
void serialize_base(archive& ar, const unsigned int version) {
ar & BOOST_SERIALIZATION_BASE_OBJECT_NVP(AttributeBase);
ar & boost::serialization::make_nvp("Value", *_data);
}
void serialize(boost::archive::text_oarchive& oa, const unsigned int version) {
std::cout << "Attribute::serialize" << std::endl;
serialize_base(oa, version);
}
void serialize(boost::archive::text_iarchive& ia, const unsigned int version) {
serialize_base(ia, version);
}
};
class AttributeContainer : public NetworkSerializable {
friend class boost::serialization::access;
public:
std::map<std::string, AttributeBase*> _attributes;
AttributeContainer() {};
virtual ~AttributeContainer() {};
template <typename _T>
void RegisterAttribute(const std::string& name, const std::string& description, _T* var) {
std::map<std::string, AttributeBase*>::const_iterator pos;
if ( (pos = _attributes.find(name)) == _attributes.end() ) {
Attribute<_T>* attribute = new Attribute<_T>(name, description, var);
_attributes.insert(std::map<std::string, AttributeBase*>::value_type(name, attribute));
}
};
template <class archive>
void serialize_attributes(archive& ar, const unsigned int version) {
ar & _attributes;
};
void serialize(boost::archive::text_oarchive& oa, const unsigned int version) {
std::cout << "AttributeContainer::serialize" << std::endl;
serialize_attributes(oa, version);
}
void serialize(boost::archive::text_iarchive& ia, const unsigned int version) {
serialize_attributes(ia, version);
}
}; // end class AtributeContainer
class Tab : public AttributeContainer {
friend class boost::serialization::access;
public:
Tab(const std::string tabName)
: _tabName(tabName) {}
virtual ~Tab() {}
protected:
Tab()
: _tabName("") {}
template<class archive>
inline void serialize_attributes(archive& ar, const unsigned int version) {
// ar & BOOST_SERIALIZATION_BASE_OBJECT_NVP(AttributeContainer);
ar & boost::serialization::base_object<AttributeContainer>(*this);
ar & boost::serialization::make_nvp("TabName", _tabName);
}
void serialize(boost::archive::text_oarchive& oa, const unsigned int version) {
std::cout << "Tab::serialize" << std::endl;
serialize_attributes(oa, version);
}
void serialize(boost::archive::text_iarchive& ia, const unsigned int version) {
serialize_attributes(ia, version);
}
private:
std::string _tabName;
};
void test() {
std::ostringstream oarchiveStream;
boost::archive::text_oarchive outputArchive(oarchiveStream);
Tab* tab = new Tab("temp");
bool tempBool = true;
tab->RegisterAttribute("tempBool", "a temp boolean", &tempBool);
std::string tempString("1234");
tab->RegisterAttribute("tempString", "a temp string", &tempString);
outputArchive << tab;
}
} // namespace serial_test
BOOST_SERIALIZATION_ASSUME_ABSTRACT(serial_test::AttributeBase);
BOOST_CLASS_EXPORT_KEY(serial_test::AttributeBase);
BOOST_CLASS_EXPORT_KEY(serial_test::Attribute<bool>);
BOOST_CLASS_EXPORT_KEY(serial_test::Attribute<string>);
BOOST_CLASS_EXPORT_IMPLEMENT(serial_test::Attribute<bool>);
BOOST_CLASS_EXPORT_IMPLEMENT(serial_test::Attribute<string>);
BOOST_CLASS_EXPORT_KEY(serial_test::Tab);
BOOST_CLASS_EXPORT_IMPLEMENT(serial_test::Tab);

Related

Serializing derived classes from an abstract class using boost::serialization

I am trying to serialize derived classes from an abstract class and I am getting some (obvious) errors.
Here is the code (ran using g++ -std=c++17 XXXX.cpp -Wall -larmadillo -lboost_serialization):
#include <boost/archive/binary_iarchive.hpp>
#include <boost/archive/binary_oarchive.hpp>
#include <boost/serialization/assume_abstract.hpp>
#include <boost/serialization/serialization.hpp>
#include <boost/serialization/split_member.hpp>
#include <boost/serialization/export.hpp>
#include <boost/serialization/unique_ptr.hpp>
#include <fstream>
#include <iostream>
#include <memory>
#include <sstream>
#include <vector>
class Car {
public:
//Car() = default;
//virtual double getInfo() = 0;
virtual char const* type() const = 0;
virtual ~Car() = default;
private:
friend class boost::serialization::access;
template <class Archive>
void serialize(Archive& ar, const unsigned int version) {};
};
class Audi : public Car {
public:
char const* type() const override { return "Audi"; }
//double getInfo() override { return 2.2; }
Audi(std::string owner1, int hp1, std::string second_owner1, std::string country1)
{
owner = owner1;
hp = hp1;
second_owner = second_owner1;
country = country1;
}
Audi() = default;
std::string owner;
int hp{};
std::string second_owner;
std::string country;
private:
friend class boost::serialization::access;
template <class Archive>
void serialize(Archive& ar, const unsigned int version) {
ar & boost::serialization::base_object<Car>(*this); //https://theboostcpplibraries.com/boost.serialization-class-hierarchies
ar & owner;
ar & hp;
ar & second_owner;
ar & country;
}
};
BOOST_CLASS_EXPORT(Audi);
BOOST_SERIALIZATION_ASSUME_ABSTRACT(Car); //Tell Boost that Car is abstract
int main() {
std::string str;
{
std::unique_ptr<Car> audi = std::make_unique<Audi>("Wilma", 3, "Rene", "Argentina");
audi->type();
std::stringstream strs;
boost::archive::binary_oarchive ar(strs);
ar& audi;
str = strs.str();
}
{
std::unique_ptr<Car> audi; //= std::make_unique<Audi>();
std::stringstream strs(str);
boost::archive::binary_iarchive ar(strs);
ar& audi;
//dynamic_cast<Audi>(audi); Tried using casting but am unfamiliar with it -- perhaps this is the issue?
std::cout << "audi: hp=" << audi->hp << " owner=" << audi->owner <<"\n";
}
}
and I am getting the errors:
audi_seri.cpp: In function ‘int main()’:
audi_seri.cpp:84:36: error: cannot dynamic_cast ‘audi’ (of type ‘class std::unique_ptr<Car>’) to type ‘class Audi’ (target is not pointer or reference)
dynamic_cast<Audi>(audi);
^
audi_seri.cpp:86:47: error: ‘class Car’ has no member named ‘hp’
std::cout << "audi: hp=" << audi->hp << " owner=" << audi->owner <<"\n";
^~
audi_seri.cpp:86:72: error: ‘class Car’ has no member named ‘owner’
std::cout << "audi: hp=" << audi->hp << " owner=" << audi->owner <<"\n";
Now, I know why this is happening -- Car doesn't have these variables. But how can I solve this to where the solution can work for many derived classes that could have different members? Perhaps to solve this I have to use casting?
To give more insight as to why I am trying to do this. I initially wanted to have void serialize() in Car as a virtual function but that doesn't work with templates. And hence I came to the above code with the help of https://theboostcpplibraries.com/boost.serialization-class-hierarchies
Here is the old code with with void serialize as a virtual function:
class Car {
public:
virtual double getInfo() = 0;
private:
template <typename Archive> //This is the problem I am trying to solve: templates cannot be virtual
virtual void serialize(Archive& ar, unsigned int version) = 0;
};
class Audi : public Car {
public:
char const* type() const override { return "Audi"; }
//double getInfo() override { return 2.2; }
Audi(std::string owner1, int hp1, std::string second_owner1, std::string country1)
{
owner = owner1;
hp = hp1;
second_owner = second_owner1;
country = country1;
}
Audi() = default;
std::string owner;
int hp{};
std::string second_owner;
std::string country;
private:
template <class Archive>
void serialize(Archive& ar, const unsigned int version) {
ar & owner;
ar & hp;
ar & second_owner;
ar & country;
}
};
BOOST_SERIALIZATION_ASSUME_ABSTRACT(Car); //Tell Boost that Car is abstract
int main() {
std::string str;
{
std::unique_ptr<Car> audi = std::make_unique<Audi>("Wilma", 3, "Rene", "Argentina");
std::stringstream strs;
boost::archive::binary_oarchive ar(strs);
ar& audi;
str = strs.str();
}
{
std::unique_ptr<Car> audi; //= std::make_unique<Audi>();
std::stringstream strs(str);
boost::archive::binary_iarchive ar(strs);
ar& audi;
//dynamic_cast<Audi>(audi);
std::cout << "audi: hp=" << audi->hp << audi->hp<< " owner=" << "\n";
}
}

Boost missing some data during serialization process

In my setup i have vector of OpacityChannel pointer as a member of Container class.
if i create a varible of OpacityChannel and write it to the archive than everything is written as expected.
when i write Container class object and write it to archive than it misses the data for KeyframeFloat class which is a member of OpacityChannel class.
This is my main file if i change the line from ar & cont; to ar & opacityChannel than it writes the data as required.
I am not able to understand why it is missing data when i write Container class.
#include "pch.h"
#include <iostream>
#include<fstream>
#include <boost/archive/text_iarchive.hpp>
#include <boost/archive/text_oarchive.hpp>
#include "Container.h"
#include "KeyframeFloat.h"
#include <boost/serialization/export.hpp>
BOOST_CLASS_EXPORT_GUID(SumOpacity_Channel, "SumOpacity_Channel")
BOOST_CLASS_EXPORT_GUID(SumKeyframeFloat, "SumKeyframeFloat")
int main()
{
const char* fileName = "saved.txt";
std::vector<int> vec;
Container cont;
SumOpacity_Channel opacityChannel;
SumKeyframeFloat key1, key2;
opacityChannel.AddKeyframe(key1);
opacityChannel.AddKeyframe(key2);
cont.AddChannel(&opacityChannel);
SumKeyframeFloat key1_Restored, key2_Restored;
{
// Create an output archive
std::ofstream ofs(fileName);
boost::archive::text_oarchive ar(ofs);
ar & cont; // KeyframeFloat data is not written.
// if i do ar & opacityChannel; than keyframeFloat data is written in archive
}
Container c_Restored ;
SumOpacity_Channel opacityChannel_Restored;
//load data
{
//create an input stream
std::ifstream ifs(fileName);
boost::archive::text_iarchive ar(ifs);
ar & c_Restored ;
}
do
{
std::cout << '\n' << "Press a key to continue...";
} while (std::cin.get() != '\n');
}
This is the container Class
#pragma once
#include <boost/serialization/base_object.hpp>
#include <boost/serialization/split_member.hpp>
#include "SumChannel.h"
#include "SumOpacityChannel.h"
#include <boost/serialization/vector.hpp>
#include <memory>
#include <boost/serialization/export.hpp>
class Container
{
private:
std::vector< SumOpacity_Channel* > Channels;
public:
Container() {} ;
~Container()
{
if(Channels.size() > 0 )
for (int i = 0; i < Channels.size(); i++)
{
delete Channels[i];
}
}
Container(const Container& c)
{
if (Channels.size() > 0)
Channels.clear(); // clear any previous channels
for (int i = 0; i < c.Channels.size(); i++)
{
Channels.push_back(c.Channels[i]->Clone());
}
}
Container& operator=(const Container& c)
{
if (Channels.size() > 0)
Channels.clear(); // clear any previous channels
for (int i = 0; i < c.Channels.size(); i++)
{
Channels.push_back(c.Channels[i]->Clone());
}
return *this;
}
void AddChannel(SumOpacity_Channel* channel)
{
Channels.push_back(channel->Clone());
}
private:
friend class boost::serialization::access;
template <typename Archive>
void save(Archive& ar, const unsigned version) const {
ar & Channels;
}
template <typename Archive>
void load(Archive& ar, const unsigned version) {
ar & Channels;
}
BOOST_SERIALIZATION_SPLIT_MEMBER()
};
This is the keyframeFloat class.
#pragma once
#include "KeyframeBase.h"
#include <boost/serialization/split_member.hpp>
#include <boost/serialization/serialization.hpp>
class SumKeyframeFloat
{
private:
float x;
public:
SumKeyframeFloat() { this->x = 0.0; };
SumKeyframeFloat(float xvalue) : x(xvalue) { };
SumKeyframeFloat(const SumKeyframeFloat& key) : x ( key.x) { };
~SumKeyframeFloat() = default;
private:
friend class boost::serialization::access;
template<typename Archive>
void save(Archive& ar, const unsigned int version) const {
std::cout << "writing float keyframe to Archive" << std::endl;
ar & x;
}
template<typename Archive>
void load(Archive& ar, const unsigned int version) {
ar & x;
}
BOOST_SERIALIZATION_SPLIT_MEMBER()
};
This is sum Opacity class header and defination
#include <string>
#include "SumChannel.h"
#include <iostream>
#include <vector>
#include <boost/serialization/split_member.hpp>
#include <boost/serialization/base_object.hpp>
#include <boost/serialization/export.hpp>
#include <boost/serialization/serialization.hpp>
#include <boost/serialization/vector.hpp>
class SumOpacity_Channel
{
private:
std::vector<SumKeyframeFloat> keyframes;
public:
SumOpacity_Channel();
SumOpacity_Channel(const SumOpacity_Channel &opacityChannel);
~SumOpacity_Channel() = default;
SumOpacity_Channel& operator=(const SumOpacity_Channel &posChannel);
void AddKeyframe(SumKeyframeFloat key);
void DeleteKeyframe(SumKeyframeFloat key, int number);
SumOpacity_Channel* Clone() const;
public:
friend class boost::serialization::access;
template<typename Archive>
void save(Archive& ar, const unsigned int version) const {
ar & keyframes;
}
template<typename Archive>
void load(Archive& ar, const unsigned int version) {
ar & keyframes;
}
BOOST_SERIALIZATION_SPLIT_MEMBER()
};
////////////////////////////////////////////////
#include "pch.h"
#include "SumOpacityChannel.h"
SumOpacity_Channel::SumOpacity_Channel()
{
}
/////////////////////////////////////////////////////////////////////////////////////////////
SumOpacity_Channel::SumOpacity_Channel(const SumOpacity_Channel &opacityChannel)
{
this->keyframes.clear();
for (auto KeyFrame : keyframes)
{
this->keyframes.push_back(KeyFrame);
}
}
SumOpacity_Channel& SumOpacity_Channel::operator=(const SumOpacity_Channel &opacityChannel)
{
for (auto KeyFrame : keyframes)
{
this->keyframes.push_back(KeyFrame);
}
return *this;
}
void SumOpacity_Channel::AddKeyframe(SumKeyframeFloat key)
{
keyframes.push_back(key);
}
void SumOpacity_Channel::DeleteKeyframe(SumKeyframeFloat key, int number)
{
keyframes.erase(keyframes.begin() + number);
}
SumOpacity_Channel* SumOpacity_Channel::Clone() const
{
return new SumOpacity_Channel(*this);
}
Your channel is empty.
This copy constructor doesn't make any sense:
SumOpacity_Channel::SumOpacity_Channel(const SumOpacity_Channel &opacityChannel)
{
this->keyframes.clear(); // [1]
for (auto KeyFrame : keyframes) // [2]
{
this->keyframes.push_back(KeyFrame);
}
}
in [2] you are iterating over empty vector - it was cleared the line above.
Also why clear is called? It is constructor, so this vector could not be filled ever before. Calling clear makes sense in copy assignment operator, but not here.
So this
cont.AddChannel(&opacityChannel);
with that
void AddChannel(SumOpacity_Channel* channel)
{
Channels.push_back(channel->Clone());
}
SumOpacity_Channel* SumOpacity_Channel::Clone() const
{
return new SumOpacity_Channel(*this);
}
gives empty vector.
auto KeyFrame : keyframes
should be
auto KeyFrame : opacityChannel.keyframes
?

Boost serialization of derived class with private members

I try to serialize a class, say B (in file b.h), which is derived from another one, say A (in file a.h). Both classes have private members and I want to serialize both with the boost serialization library non-intrusively. The serialization/deserialization of A does work so far. For the same for the derived class one would use
ar & boost::serialization::base_object<base_class>(*this);
when the intrusive method is used, but where to put it in the non-intrusive case (save/load/serialize or all three?)? And what object has to been used in place of the this pointer?
In the productive code I have derived class a bit more complicated than B. There I got a compiler error which I wasn't able to reproduce in this small example. The compiler message (MSVC 2015, C2665, translated in English):
'boost::serialization::save' : none of the number1 overloads can convert parameter number2 from type 'type'
The Error in German:
Fehler C2665 "boost::serialization::save": Durch keine der 3 Überladungen konnten alle Argumenttypen konvertiert werden. CalorCLI c:\boost_1_61_0\boost\serialization\split_free.hpp 45
Could anyone help?
The Code of a.h :
#pragma once
class A {
private:
int elemA;
public:
A() = default;
A(int elem) : elemA(elem) {};
virtual ~A() = default;
int getElemA() const { return elemA; }
void setElemA(int elem) {
elemA = elem;
}
};
The code of b.h :
#pragma once
#include "a.h"
class B : public A {
private:
int elemB;
public:
B() = default;
B(int elem) : elemB(elem) {};
virtual ~B() = default;
int getElemB() const { return elemB; }
void setElemB(int elem) { elemB = elem; }
};
The Code of the main program:
// TestSerialization.cpp : Definiert den Einstiegspunkt für die Konsolenanwendung.
//
#include <string>
#include <fstream>
#include <iostream>
#include <boost/archive/text_oarchive.hpp>
#include <boost/archive/text_iarchive.hpp>
#include "b.h"
#include "stdafx.h"
namespace boost {
namespace serialization {
template<class Archive>
void save(Archive & ar, const A & pA, const unsigned int version)
{
ar & pA.getElemA();
}
template<class Archive>
void load(Archive & ar, A & pA, const unsigned int version)
{
int n;
ar & n;
pA.setElemA(n);
}
template<class Archive>
void serialize(Archive & ar, A & pA, const unsigned int version)
{
boost::serialization::split_free(ar, pA, version);
}
template<class Archive>
void save(Archive & ar, const B & pB, const unsigned int version)
{
ar & pB.getElemB();
}
template<class Archive>
void load(Archive & ar, B & pB, const unsigned int version)
{
int n;
ar & n;
pB.setElemB(n);
}
template<class Archive>
void serialize(Archive & ar, B & pB, const unsigned int version)
{
boost::serialization::split_free(ar, pB, version);
}
}
}
int main()
{
A *objA= new A(747);
{
std::ofstream ofs("SavedA");
boost::archive::text_oarchive oa(ofs);
oa << objA;
}
{
A *objA1 = new A();
std::ifstream ifs("SavedA");
boost::archive::text_iarchive ia(ifs);
ia >> objA1;
}
B *objB = new B(747);
{
std::ofstream ofs("SavedB");
boost::archive::text_oarchive oa(ofs);
oa << objB;
}
{
B *objB1 = new B();
std::ifstream ifs("SavedB");
boost::archive::text_iarchive ia(ifs);
ia >> objB1;
}
return 0;
}
First, a fair warning about Quasi-Classes (PDF). They are the enemy of encapsulation and confuse OOP.
Next, let me answer two of your questions real quick and proceed to show my take on this:
Q. where to put it in the non-intrusive case (save/load/serialize or all three?)?
Either in serialize OR in both save and load (if you have split implementations)
Q. what object has to been used in place of the this pointer?
The same object. If you do member-function serialize this points to the same object as gets passed the free function as the second argument. Just use that object.
My Take
Now, let me refer to my answer to Get private data members for non intrusive boost serialization C++
Here's a demonstration of the idea Tanner suggested in his comment
Live On WandBox
a.h
#pragma once
class A {
private:
int elemA;
public:
A(int elem = 0) : elemA(elem) {};
virtual ~A() = default;
int getElemA() const { return elemA; }
void setElemA(int elem) { elemA = elem; }
};
b.h
#pragma once
#include "a.h"
class B : public A {
private:
int elemB;
public:
B(int elem = 0) : A(42), elemB(elem) {};
int getElemB() const { return elemB; }
void setElemB(int elem) { elemB = elem; }
};
main.cpp
#include <string>
#include <sstream>
#include <iostream>
#include <boost/archive/text_oarchive.hpp>
#include <boost/archive/text_iarchive.hpp>
#include <boost/serialization/base_object.hpp>
#include <boost/serialization/export.hpp>
#include "b.h"
BOOST_CLASS_EXPORT(A)
BOOST_CLASS_EXPORT(B)
namespace privates {
template <typename Key, typename Key::type PointerToMember> struct store {
friend typename Key::type get(Key) { return PointerToMember; }
};
struct elemA {
typedef int A::*type;
friend type get(elemA); // ADL-enable
};
struct elemB {
typedef int B::*type;
friend type get(elemB); // ADL-enable
};
template struct store<elemA, &A::elemA>;
template struct store<elemB, &B::elemB>;
} // namespace privates
auto& getElemA(A& instance) { return instance.*(get(privates::elemA())); }
auto& getElemB(B& instance) { return instance.*(get(privates::elemB())); }
namespace boost {
namespace serialization {
template<class Archive>
void serialize(Archive & ar, A& v, unsigned) { ar & getElemA(v); }
template<class Archive>
void serialize(Archive & ar, B& v, unsigned) { ar & base_object<A>(v) & getElemB(v); }
}
}
template <typename T> void run_tests() {
std::stringstream ss;
{
A *obj= new T(747);
boost::archive::text_oarchive oa(ss);
oa << obj;
delete obj;
}
std::cout << ss.str() << "\n";
{
A *obj = nullptr;
boost::archive::text_iarchive ia(ss);
ia >> obj;
delete obj;
}
}
int main()
{
run_tests<A>();
run_tests<B>();
}
Note it simplifies a few things and at least removed memory-leaks when there were no exceptions.
Output Live On WandBox
22 serialization::archive 15 0 1 0
0 747
22 serialization::archive 15 1 1 B 1 0
0 1 0
1 42 747
Now I got it: Non-intrusive serialization (text format) with a pimpl style struct as described paragraph 3 here let most members private and reduced the overhead of get/set methods. xml is still open - got compiler errors C2664 and C2789 on Visual Studio 2015. Also json could be interesting ...

Serialization of derived class trips Boost assertion

When trying to serialize a derived class with boost::serialization, an assertion in boost/archive/detail/oserializer.hpp (line 436 in Boost 1.53.0) is tripped:
const basic_pointer_oserializer * bpos
= static_cast<const basic_pointer_oserializer *>(
boost::serialization::singleton<
archive_serializer_map<Archive>
>::get_const_instance().find(*true_type)
);
BOOST_ASSERT(NULL != bpos);
Here is my MWE:
#include <iostream>
#include <fstream>
#include <boost/archive/polymorphic_xml_oarchive.hpp>
#include <boost/serialization/nvp.hpp>
#include <boost/serialization/access.hpp>
#include <boost/serialization/base_object.hpp>
class base
{
friend class boost::serialization::access;
template <class Archive>
void serialize(Archive &ar, const unsigned int version)
{
ar & boost::serialization::make_nvp("test", test);
}
int test;
public:
virtual void print() { std::cout << 1 << std::endl; }
};
class derived : public base
{
friend class boost::serialization::access;
template <class Archive>
void serialize(Archive &ar, const unsigned int version)
{
ar & boost::serialization::make_nvp("base", boost::serialization::base_object<base>(*this));
ar & boost::serialization::make_nvp("test2", test2);
}
int test2;
public:
void print() { std::cout << 2 << std::endl; }
};
int main (int argc, char const *argv[])
{
base *b = new derived();
std::ofstream stream("out.xml");
boost::archive::polymorphic_xml_oarchive ar(stream);
ar.register_type<derived>();
ar << boost::serialization::make_nvp("b", b);
return 0;
}
I also tried using BOOST_CLASS_EXPORT_GUID instead of register_type, but that also didn't work. Any idea what I'm doing wrong?
You probably don't mean to use polymorphic_xml_archive (see docs).
Notably, polymorphic archives have to do with the interface of the archives, nothing with the entities you're serializing for (they can always be polymorphic).
If you use xml_archive instead, the problem vanishes.

correct type-cast for boost de-serializations of different derived classes

In my application, there are agents of different types. I am planning to use boost serialization for sending/receiving data between agents.(by sending/receiving, I actually mean writing/reading operation the serialization target file)
A receiving agent may receive data of different data whose type is not known in advance. Suppose the data format has a general structure like this:
class Base
{
public:
int message_type_id;
}
class Derived_1
{
public:
Derived_1(int message_type_id_):message_type_id(message_type_id_){}
struct data_1 {...};
};
class Derived_2
{
public:
Derived_2(int message_type_id_):message_type_id(message_type_id_){}
struct data_2 {...};
};
the sending agent can send(i.e serialize) any of the two derived types. Similarly, the receiving agent can receive(i.e de-serialize) any of the two derived types; while what I can see in the tutorial(Dumping derived classes through base class pointers) is like this:
void save()
{
std::ofstream file("archive.xml"); //target file
boost::archive::xml_oarchive oa(file);
oa.register_type<date>( );// you know what you are sending, so you make proper modifications here to do proper registration
base* b = new date(15, 8, 1947);
oa & BOOST_SERIALIZATION_NVP(b);
}
void load()
{
std::ifstream file("archive.xml"); //target file
boost::archive::xml_iarchive ia(file);
ia.register_type<date>( );// I don't know which derived class I am receiving, so I can't do a proper registration
base *dr;
ia >> BOOST_SERIALIZATION_NVP(dr);
date* dr2 = dynamic_cast<date*> (dr);
std::cout << dr2;
}
as you can see, xml_oarchive and xml_iarchive do a register_type<date> before serialize/deserialization. so the receiving end will know in advance what to dynamic_cast to.
whereas in my case, since I know what I am sending, I can do proper registration&serialization on case-to-case basis. However, on the receiving end, I dont't know in advance what to register and what to dynamic cast.
Is there a way I can tell the type in advance so that the receiving can do a casting?
thanks
EDIT:
Here is the simplified modification of demo.cpp I save an object, and then restore it.
#include <cstddef> // NULL
#include <iomanip>
#include <iostream>
#include <fstream>
#include <string>
#include <boost/archive/tmpdir.hpp>
#include <boost/archive/text_iarchive.hpp>
#include <boost/archive/text_oarchive.hpp>
#include <boost/serialization/base_object.hpp>
#include <boost/serialization/utility.hpp>
#include <boost/serialization/list.hpp>
#include <boost/serialization/assume_abstract.hpp>
/*
bus_stop is the base class.
bus_stop_corner and bus_stop_destination are derived classes from the above base class.
bus_route has a container that stores pointer to the above derived classes
*/
class bus_stop
{
friend class boost::serialization::access;
virtual std::string description() const = 0;
template<class Archive>
void serialize(Archive &ar, const unsigned int version)
{
ar & type;
}
protected:
public:
std::string type;
bus_stop(){type = "Base";}
virtual ~bus_stop(){}
};
BOOST_SERIALIZATION_ASSUME_ABSTRACT(bus_stop)
class bus_stop_corner : public bus_stop
{
friend class boost::serialization::access;
virtual std::string description() const
{
return street1 + " and " + street2;
}
template<class Archive>
void serialize(Archive &ar, const unsigned int version)
{
// save/load base class information
ar & boost::serialization::base_object<bus_stop>(*this);
ar & street1 & street2;
}
public:
std::string street1;
std::string street2;
bus_stop_corner(){}
bus_stop_corner(
const std::string & _s1, const std::string & _s2
) :
street1(_s1), street2(_s2)
{
type = "derived_bs_corner";
}
};
class bus_stop_destination : public bus_stop
{
friend class boost::serialization::access;
virtual std::string description() const
{
return name;
}
template<class Archive>
void serialize(Archive &ar, const unsigned int version)
{
ar & boost::serialization::base_object<bus_stop>(*this) & name;
}
public:
std::string name;
bus_stop_destination(){}
bus_stop_destination(
const std::string & _name
) :
name(_name)
{
type = "derived_bs_destination";
}
};
class bus_route
{
friend class boost::serialization::access;
typedef bus_stop * bus_stop_pointer;
template<class Archive>
void serialize(Archive &ar, const unsigned int version)
{
ar.register_type(static_cast<bus_stop_corner *>(NULL));
ar.register_type(static_cast<bus_stop_destination *>(NULL));
ar & stops;
}
public:
std::list<bus_stop_pointer> stops;
bus_route(){}
void append(bus_stop *_bs)
{
stops.insert(stops.end(), _bs);
}
};
//BOOST_CLASS_VERSION(bus_route, 2)
void save_schedule(const bus_route s, const char * filename){
// make an archive
std::ofstream ofs(filename);
boost::archive::text_oarchive oa(ofs);
oa << s;
}
void
restore_schedule(bus_route &s, const char * filename)
{
// open the archive
std::ifstream ifs(filename);
boost::archive::text_iarchive ia(ifs);
// restore the schedule from the archive
ia >> s;
}
int main(int argc, char *argv[])
{
bus_stop *bs1 = new bus_stop_corner(
"First St", "Second st"
);
bus_stop *bs2 = new bus_stop_destination(
"myName"
);
// make a routes
bus_route original_route;
original_route.append(bs1);
original_route.append(bs2);
std::string filename1(boost::archive::tmpdir());
filename1 += "/demofile1.txt";
save_schedule(original_route, filename1.c_str());
bus_route new_route ;
restore_schedule(new_route, filename1.c_str());
////////////////////////////////////////////////////////
std::string filename2(boost::archive::tmpdir());
filename2 += "/demofile2.txt";
save_schedule(new_route, filename2.c_str());
delete bs1;
delete bs2;
return 0;
}
The old and new objects are not equal coz again saving(serializing) the new object to another file results in a different(empty) content. Can you please let me know how I can fix this code to deserialize the derived classes successfully? many thanks
EDIT-2
There is nothing wrong with the above code now(after a small typo was fixed).
I am answering my own question here coz there is another good approach suggested by someone else.
So the Answer to my first question is like this :
As long as you register the derived types in the main serialization function (in the above case: serialize() in bus_route class) everything should be fine.
thanks for all the help
A solution is to (de-)serialize boost::shared_ptr<Base>. The following code demonstrates it. After deserialization the pDst is an instance of the Derived_1 class. The code complied using an online compiler is available on this link.
#include <boost/serialization/access.hpp>
#include <boost/serialization/assume_abstract.hpp>
#include <boost/serialization/shared_ptr.hpp>
#include <boost/serialization/nvp.hpp>
#include <boost/noncopyable.hpp>
#include <boost/make_shared.hpp>
#include <boost/smart_ptr/shared_ptr.hpp>
class Base {
friend class boost::serialization::access;
public:
Base();
virtual ~Base();
private:
template<class Archive> void serialize(Archive &ar, const unsigned int version) {}
public:
virtual bool operator ==(const Base &rh) const = 0;
};
BOOST_SERIALIZATION_ASSUME_ABSTRACT(Base)
BOOST_SERIALIZATION_SHARED_PTR(Base)
Base::Base() {
}
Base::~Base() {
}
class Derived_1 : boost::noncopyable, public Base {
friend class boost::serialization::access;
public:
int m_iValue;
public:
Derived_1();
Derived_1(int iValue);
private:
template<class Archive> void serialize(Archive &ar, const unsigned int version) {
ar & BOOST_SERIALIZATION_BASE_OBJECT_NVP(Base);
ar & boost::serialization::make_nvp("value", m_iValue);
}
public:
bool operator ==(const Base &rh) const;
};
BOOST_SERIALIZATION_SHARED_PTR(Derived_1)
Derived_1::Derived_1() : m_iValue(0) {
}
Derived_1::Derived_1(int iValue) : m_iValue(iValue) {
}
bool Derived_1::operator==(const Base &rh) const {
const Derived_1 *pRH = dynamic_cast<const Derived_1 *>(&rh);
return pRH != nullptr && pRH->m_iValue == this->m_iValue;
}
#include <boost/serialization/export.hpp>
#include <boost/archive/xml_oarchive.hpp>
#include <boost/archive/xml_iarchive.hpp>
#include <boost/make_shared.hpp>
#include <sstream>
#include <string>
BOOST_CLASS_EXPORT_GUID(Base, "base")
BOOST_CLASS_EXPORT_GUID(Derived_1, "derived_1")
void test(void) {
std::string str;
boost::shared_ptr<Base> pSrc = boost::make_shared<Derived_1>(10);
boost::shared_ptr<Base> pDst;
{
std::ostringstream ofs;
boost::archive::xml_oarchive oa(ofs);
oa << boost::serialization::make_nvp("item", pSrc);
str = ofs.str();
}
{
std::istringstream ifs(str);
boost::archive::xml_iarchive ia(ifs);
ia >> boost::serialization::make_nvp("item", pDst);
}
if (*pSrc == *pDst) {
printf("Success\n");
}
else {
printf("Fail\n");
}
}
int main(int argc, char* argv[]) {
test();
}