Is it possible to use a polymorphic_binary_iarchive to deserialize an object serialized with binary_oarchive?
My library has been written using binary archives for all the serialisation methods (as well as EOS portable archives). This results in overloaded serialize methods everywhere, significant code bloat and link time cost. I'd like to switch to polymorphic archive methods to reduce the number of exposed serialize methods and facilitate easy use of other archives (in particular XML). However, I would also like to maintain backwards compatibility with already serialized data.
polymorphic_binary_iarchive seems to work usually, but there is at least one case where it fails: vector.hpp changes the method of serialization depending on use_array_optimization, which for binary archives is set for any type that is_bitwise_serializable. So types that contain vector<size_t> fail to deserialize.
I'm tempted to try to specialize use_array_optimization for polymorphic_binary_iarchive (and the oarchive similarly). Is there any reason this would not work, or any other reasons why this is just A Bad Idea(TM)?
Edit
I tried that specialization:
struct use_array_optimization_in_polymorphic_archive {
template <class T>
struct apply : public boost::serialization::is_bitwise_serializable< T > {};
};
namespace boost {
namespace serialization {
template <>
struct use_array_optimization<boost::archive::polymorphic_iarchive> {
template <class ValueType>
struct apply : boost::mpl::apply1<
use_array_optimization_in_polymorphic_archive,
BOOST_DEDUCED_TYPENAME boost::remove_const<ValueType>::type>::type {};
};
}}
But it doesn't work because array.hpp then expects polymorphic_iarchive to implement load_array, which is only implemented for a basic_binary_iprimitive.
I'm also concerned that this will change behaviour for all polymorphic_iarchive implementations, not just the polymorphic_binary_iarchive. More thought required...
Edit2
Herefollows some code by way of demonstration. Toggle POLY_ON to use the polymorphic archive to deserialize; this works for doubles. Toggle VEC_ON to use vectors, demonstrating the problem. NB: I haven't yet double checked that this is exactly the same problem, but I'm reasonably sure it is. NNB: This is using Boost 1.59.
#include <fstream>
#include <boost/archive/binary_oarchive.hpp>
// #define POLY_ON
#ifdef POLY_ON
#include <boost/archive/polymorphic_binary_iarchive.hpp>
#else
#include <boost/archive/binary_iarchive.hpp>
#endif
// #define VEC_ON
#ifdef VEC_ON
#include <vector>
#include <boost/serialization/vector.hpp>
#endif
class bank_balance {
private:
friend class boost::serialization::access;
template <class archive>
void serialize(archive& ar, const unsigned int version) {
ar & date_;
ar & rate_;
}
#ifdef VEC_ON
std::vector<double> date_;
std::vector<double> rate_;
#else
double date_;
double rate_;
#endif
public:
bank_balance() : date_(0) {}
bank_balance(
#ifdef VEC_ON
std::vector<double> date, std::vector<double> rate
#else
double date, double rate
#endif
)
: date_(date), rate_(rate)
{}
bool operator==(const bank_balance& other) const {
return date_ == other.date_ && rate_ == other.rate_;
}
};
int main() {
std::ofstream ofs("bank_balance.ser");
#ifdef VEC_ON
const bank_balance balance({45367, 45369}, {5.6, 2.43});
#else
const bank_balance balance(45367, 5.6);
#endif
{
boost::archive::binary_oarchive oa(ofs);
oa << balance;
}
bank_balance balance2;
{
std::ifstream ifs("bank_balance.ser");
#ifdef POLY_ON
boost::archive::polymorphic_binary_iarchive ia(ifs);
#else
boost::archive::binary_iarchive ia(ifs);
#endif
ia >> balance2;
}
if (balance == balance2) std::cout << "ok\n";
else std::cout << "dammit\n";
return 0;
}
Is it possible to use a polymorphic_binary_iarchive to deserialize an object serialized with binary_oarchive?
Short answer: yes.
The only difference is the call-site interface here.
EDIT
Perhaps, unintentionally, this "promise" was broken when they introduced optimized serialization for POD containers.
Here's my analysis, with yours.cpp from your question, and mine.cpp as edited below:
{
std::ofstream ofs("bank_balance.ser");
#ifdef POLY_ON
boost::archive::polymorphic_binary_oarchive oa(ofs);
#else
boost::archive::binary_oarchive oa(ofs);
#endif
oa << balance;
}
I compile all flavours with the following command line:
for src in yours mine; do for a in {,-DPOLY_ON}\ {,-DVEC_ON}; do time g++ $a -O2 -std=c++11 $src.cpp -lboost_{system,serialization} -o "$src${a//[D _]/}.exe"; done; done
Which results in mine.exe, mine-POLYON.exe, mine-POLYON-VECON.exe, mine-VECON.exe, yours.exe, yours-POLYON.exe, yours-POLYON-VECON.exe and yours-VECON.exe. Running them:
(set -x; for a in ./*.exe; do $a; done)
Results in
+ ./mine.exe
ok: true
+ ./mine-POLYON.exe
ok: true
+ ./mine-POLYON-VECON.exe
ok: true
+ ./mine-VECON.exe
ok: true
+ ./yours.exe
ok
+ ./yours-POLYON.exe
ok
+ ./yours-POLYON-VECON.exe
terminate called after throwing an instance of 'std::length_error'
what(): vector::_M_default_append
+ ./yours-VECON.exe
ok
Note that all combinations are fine if you write using the same archive implementation as while reading. You're also right that sadly ./yours-POLYON-VECON.exe is the only one to break. I think this is unintentional but your hunch could be spot on:
doc
Note that the concept of polymophic archives is fundamentally incompatible with the serialization of new types that are marked "primitive" by the user with:
BOOST_CLASS_IMPLEMENTATION(my_primitive_type, boost::serialization::primitive_type)
Code to implement serialization for these types is instantiated "on the fly" in the user's program. But this conflicts with the whole purpose of the polymorphic archive. An attempt to serialize such a primitive type will result in a compilation error since the common polymorhic interface is static and cannot instantiate code for a new type.
It looks like the vector optimization path might be sharing one of these code paths.
RECOMMENDATION
I'd recommend making a conversion tool to convert old format files to the new format. You can read using the non-polymorphic iarchive and write using the polymorphic archive. That, of course, means you'll have to compile both approaches for this release, but
it doesn't have to be baked into the main executable(s) - only the conversion tools does need the "old" method
the conversion tool will not have to be versioned, it stays the same for all future releases; this means you can stop building new versions and drop the required extra code.
I know this is an old topic, but I've been fighting with this precise problem, and think I have found a good solution.
The core of the problem is that polymorphic archives never get dispatched to use the optimized load/save versions in boost/serialization/vector.hpp. How I fixed this was to overload those dispatch functions for the polymorphic archive types like this:
namespace boost::serialization
{
template<class U, class Allocator>
inline void load(boost::archive::polymorphic_iarchive& ar, std::vector<U, Allocator>& t, const unsigned int file_version)
{
using use_optimized = boost::has_trivial_constructor<U>::type;
load(ar, t, file_version, use_optimized());
}
template<class U, class Allocator>
inline void save(boost::archive::polymorphic_oarchive& ar, const std::vector<U, Allocator>& t, const unsigned int file_version)
{
using use_optimized = boost::has_trivial_constructor<U>::type;
save(ar, t, file_version, use_optimized());
}
}
Related
I'm trying to serialize my abstract class according to those questions:
Get private data members for non intrusive boost serialization C++
Error serializing an abstract class with boost
Error serializing an abstract class with boost
My neuron.h looks like this:
class Neuron {
public:
struct access;
API virtual ~Neuron();
API virtual double activate( double x, double b ) = 0;
};
I have to keep all the Boost related members in neuron.cpp to prevent including Boost headers when using neuron.h in some other codes.
My neuron.cpp looks like this:
#include "Neuron.h"
#include <boost/archive/text_oarchive.hpp>
#include <boost/archive/text_iarchive.hpp>
BOOST_SERIALIZATION_ASSUME_ABSTRACT(Neuron);
struct Neuron :: access {
template <class Archive>
static void serialize(Archive &ar, Neuron& n, const unsigned int version) {}
};
namespace boost {
namespace serialization {
template<class Archive>
void serialize(Archive & ar, Neuron& n, const unsigned int version)
{
Neuron::access::serialize(ar, n, version);
}
} // namespace serialization
} // namespace boost
Neuron::~Neuron() {
}
The problem is, that when I'm using its inherited classes elsewhere, I'm getting the error
***/boost/boost/serialization/access.hpp:116:11: error: ‘class Neuron’ has no member named ‘serialize’
What am I doing wrong here?
I think the key here is "when I'm using its inherited classes elsewhere". Correct me (and your question, please) if I'm wrong, but this suggests that you are getting the compile error while compiling a source file other than neuron.cpp.
This makes sense, given what the compiler has to work with. You might have noticed that changes to one source file tend to not require re-compiling other source files. So adding something -- like an overload of serialize() -- to neuron.cpp does not change how other translation units are compiled. (It can change how everything is linked together in the end, but we're not there yet.) If another translation unit tries to serialize Neuron, the stuff in neuron.cpp does not matter. The compiler is not aware of an appropriate overload of serialize(), so serializing Neuron in another source file results in intrusive serialization. That is, the compiler will look for a member function of Neuron called serialize().
In order for your overload of serialize() to affect how other translation units are compiled, it needs to be declared in a header file.
Since you cannot put Boost stuff in neuron.h, you might have to create a new header file, say neuron_boost.h. This file would #include "neuron.h" then provide the declarations needed for Boost serialization. Source files that serialize descendants of Neuron would include neuron_boost.h while other source files could continue to include the original neuron.h.
I have a core data structure that I am loading values into: it is a hashmap of vectors. The vectors contain a struct, however. Further, the struct uses a template type.
I need to serialize this data structure and save to the disk periodically. Then, later--in a different program--I need to load the serialized data structure.
Here is a streamlined version of the structs. I minimally define them, but there are other data items (members), in addition to this bare bones version.
#include<vector>
#include<string>
#include<map>
#include<fstream>
#include<stdlib.h>
#include <boost/serialization/vector.hpp>
#include <boost/serialization/map.hpp>
#include <boost/archive/text_oarchive.hpp>
using namespace std;
template<typename T>
struct DataUnit{
size_t time;
string transaction_string;
T transaction;
}
template<typename T>
struct DataStructure{
map<string transaction_hash, vector<DataUnit<T>> > hashmap;
int max_transactions;
// I have a method to add stuff, but omitted for readability
}
I started with the first struct, DataUnit and modified it as follows:
template<typename T>
struct DataUnit{
size_t time;
string transaction_string;
T transaction;
template<class Archive>
void serialize(Archive & ar, const unsigned int version){
ar & time;
ar & transaction;
ar & transaction_string;
}
};
Eventually, I need to serialize the data structure. However, when I run just this with the following code:
int main(){
DataUnit<int> hi;
hi.time = time(NULL);
hi.transaction = 1;
hi.transaction_string = "world";
return 0;
}
The world blows up with errors from boost. As far as I can tell, I followed the tutorial example exactly. How do I boost-serialize these objects?
Some of the errors (but there are so many I can't believe it isn't something fundamental...):
In function `boost::archive::text_oarchive::text_oarchive(std::ostream&, unsigned int)
undefined reference to `boost::archive::text_oarchive_impl::text_oarchive_impl(std::ostream&, unsigned int)'
last error:
undefined reference to `boost::archive::archive_exception::~archive_exception()'
and it goes on from there...but I don't see where I have lacked any includes...(boost was installed via Cygwin)...
(running the code as an administrator...the text file I am outputting is present, with read write permissions...the ofs object is being created successfully)...
Currently, totally out of ideas... (tried linking lboost_serialization, reinstalling boost) No idea if I am missing something from the code ^^^
The problem is the order of your dependencies on the build command like. You need to list dependencies after the modules that use them. Also you don't compile .h files. They should be included in the .cpp files that use them. Try this command:
g++ -std=c++11 main.cpp hashmap_transaction.cpp -o run.exe -lboost_serialization
I am writing a C++ program and I have predefined objects Serial1, Serial2, Serial3, etc. I need to make a function to operate on only one of them depending on a numeric input known at compile time. I use the concatenation macro #define SER(x) Serial##x but in my main if use SER(port).read() and port is an int equal to 1, expression expands to Serialport.read() instead of Serial1.read(). However, SER(1).read() gives the required result. How can I force the preprocessor to evaluate variable port and use its value in the expansion?
BTW, I don't know the class name of Serial1, Serial2, etc so I cannot design a workaround using pointers or references
EDIT: After seeing some answers, I need to add some clarification. I need to be able to use this function with all Serial1, Serial2, etc. by calling it multiple times in a SINGLE RUN of my code. Sorry for not making that clear before!
You need to use two levels of macros to accomplish what you are trying.
#define SER2(x) Serial##x
#define SER(x) SER2(x)
Here's a test program that demonstrates the concept.
#include <iostream>
#include <string>
struct Port
{
Port(std::string name) : name_(name) {}
void write()
{
std::cout << name_ << std::endl;
}
std::string name_;
};
Port Serial1("PORT1");
Port Serial2("PORT2");
#define SER2(x) Serial##x
#define SER(x) SER2(x)
int main()
{
SER(port).write();
}
Command to build:
g++ -std=c++11 -Wall socc.cc -o socc -Dport=1
Output:
PORT1
Command to build:
g++ -std=c++11 -Wall socc.cc -o socc -Dport=2
Output:
PORT2
Update
With the updated question, the only sensible approach is to use an array of objects and use the appropriate element of the array based on the run time data.
The fact that you don't know the type doesn't really matter: decltype(Serial1) will give that. Do you know whether they all have the same type? Because if they don't, no single C++ function can return them directly. And if they don't even have a common base class, it's even harder.
The template mechanism is more powerful than the preprocessor, so it makes sense to draft that:
template<int N> struct Serial { };
template<> struct Serial<1> { static decltype(Serial1)* const ptr = &Serial1 };
template<> struct Serial<2> { static decltype(Serial2)* const ptr = &Serial3 };
template<> struct Serial<3> { static decltype(Serial3)* const ptr = &Serial3 };
// You can now use Serial<8/2-2>.ptr->
Of course, writing out the template specializations is boring. So let's get Boost.PP :
#define BOOST_PP_LOCAL_MACRO (1, 7) // Assuming you have ports 1 to 7
#define BOOST_PP_LOCAL_MACRO(n) \
template<> struct Serial<n> { \
static decltype(BOOST_PP_CAT(Serial,n))* const ptr = &Serial##n; \
};
#include BOOST_PP_LOCAL_ITERATE()
Yes, that last line is a #include without quotes. And no, I'm not sure if this is an improvement ;)
Our project uses boost::serialization to serialize many things.
But some types are not correctly registered and when serializing them we get an "unregistered class" error
I have narrowed the problem to the BOOST_CLASS_EXPORT_KEY, which, for some types are not generating code.
What BOOST_CLASS_EXPORT_KEY does is :
namespace boost {
namespace serialization {
template<>
struct guid_defined< T > : boost::mpl::true_ {};
template<>
inline const char * guid< T >(){
return K;
}
} /* serialization */
} /* boost */
All objects that are serialized inherit from a base class called Serializable.
Serialization is always done via a pointer to the base class.
This works fine except for one case:
There is a class template SerializableList which is a Serializable which holds a list of T
template< typename T>
class SerializableList
{
...
std::vector<T> m_list;
template<class Archive>
void serialize( Archive & ar, const unsigned int /*version*/ )
{
ar & boost::serialization::base_object<businessObjects::Serializable>(*this);
ar & mList;
}
};
in a dedicated cpp and hpp files we then declare each instantiation of this template to boost serialization like this:
hpp:
BOOST_CLASS_EXPORT_KEY( SerializableList<SomeT*> );
BOOST_CLASS_EXPORT_KEY( SerializableList<SomeOtherT*> );
BOOST_CLASS_EXPORT_KEY( SerializableList<AThirdT*> );
cpp:
BOOST_CLASS_EXPORT_IMPLEMENT( SerializableList<SomeT*> );
BOOST_CLASS_EXPORT_IMPLEMENT( SerializableList<SomeOtherT*> );
BOOST_CLASS_EXPORT_IMPLEMENT( SerializableList<AThirdT*> );
But half of these lines do not produce executable code in the final executable! if we put a breakpoint on each of those lines and run, half the breakpoints disappear, those who stay are on the working types (those we can serialize).
For instance the breakpoints would stay on SerializableList<SomeT*> and SerializableList<AThirdT*> but not SerializableList<SomeOtherT*>.
Btw, we have also tried to call directly boost::serialization::guid<T>(), and while it works fine for say:
boost::serialization::guid<SerializableList<SomeT*> >() which returns the key,
it doesn't for
boost::serialization::guid<SerializableList<SomeOtherT*> >() which calls the default implementation ...
So is there a compiler bug (we use Visual C++ 2010 SP1), or some good reason for the compiler to ignore some of those specializations?
I forgot to mention, all this code lies in a library, which is linked against the exe project. I've tried with different exe projects and sometimes it works sometimes it doesn't ... the compilation options are the same... I really have no clue what's going on :'(
We found the solution,
One (serializable) class had several SerializableList members, and did not include the file with all the "BOOST_CLASS_EXPORT_KEY" lines.
the other projects which were working didn't use that particular class ...
I cannot seem to get boost::serialization to work well. I have sprinkled the class .cpp files with BOOST_CLASS_EXPORT_GUID macros, after including the class and archive headers, but I still get unregistered_class exceptions.
I have looked around and it seems that whatever I can find is 1. either outdated, dealing with old versions of the library, or 2. works only for a simple one-file solution in which all the serializable classes are defined one after the other. Nothing I've found helps.
My solution at present consists of a project, compiled into a static library, that contains the core functionality with the basic archivable classes, and another test project that will eventually be fleshed out into a more concrete logic layer. Getting everything to work with boost::serialization is proving a nightmare. I'm almost tempted to write it myself.
Anyway, the class in question which raises the exception is defined in a header, which looks something like this:
#include <boost/serialization/assume_abstract.hpp>
#include <boost/serialization/serialization.hpp>
// Other includes...
namespace GameCore { class Component; }
// Forward declare some boost::serialization functions that appear at the bottom.
// ...
BOOST_SERIALIZATION_ASSUME_ABSTRACT(GameCore::Component);
namespace GameCore
{
// Some forward declares..
//////////////////////////////////////////////////////////////////////////
// Base component type.
//////////////////////////////////////////////////////////////////////////
class Component : public Updatable, public Object
{
friend class boost::serialization::access;
protected:
template <typename Archive>
friend void boost::serialization::serialize(Archive& archive, Component& object, const unsigned int version);
template <typename Archive> friend void boost::serialization::load_construct_data(Archive& archive, Component* t, const unsigned int version);
template <typename Archive> friend void boost::serialization::save_construct_data(Archive& archive, const Component* t, const unsigned int version);
public:
Component(GameObject& owner);
virtual ~Component() = 0;
// Irrelevant stuff..
GameObject& gameObject;
Transform* transform;
};
}
// The component includes have to be placed here because it would otherwise create a cyclic inclusion when trying to compile the
// individual component classes, say, Transform, which would end up including itself.
#include "Transform.h"
namespace boost
{
namespace serialization
{
template<class Archive>
inline void save_construct_data(Archive& archive, const GameCore::Component* t, const unsigned int version)
{
archive << t->gameObject;
}
template<class Archive>
inline void load_construct_data(Archive& archive, GameCore::Component* t, const unsigned int version)
{
// Retrieve data from archive required to construct new instance.
GameCore::GameObject owner;
archive >> owner;
// Invoke inplace constructor to initialize instance of class.
::new(t)GameCore::Component(owner);
}
//////////////////////////////////////////////////////////////////////////
// Serialization function for save/load.
//////////////////////////////////////////////////////////////////////////
template <typename Archive>
void serialize(Archive& archive, GameCore::Component& t, const unsigned int version)
{
archive & boost::serialization::base_object<GameCore::Object>(t);
archive & boost::serialization::base_object<GameCore::Updatable>(t);
archive & t.gameObject;
archive & t.transform;
}
}
}
That is one header file. Sorry for the verbosity. Its .cpp file starts like this:
#include "Component.h"
#include <boost/serialization/export.hpp>
#include <boost/archive/text_iarchive.hpp>
#include <boost/archive/text_oarchive.hpp>
// Other includes...
BOOST_CLASS_EXPORT_GUID(GameCore::Component, "GameCore::Component");
// Class method definitions here.
The exception is raised when I try to archive an instance object that derives from Component, which itself is an abstract class. I'm archiving through a simple method defined in a different manager class:
std::ofstream outputFile(fileName);
boost::archive::text_oarchive outputArchive(outputFile);
outputArchive << objects;
where objects is a std::list of Objects. Object is the base class from which all things, including Components, derive.
I apologize if this sounds convoluted, but there's only three layers of inheritance, and I believe I had a neat and effective architecture before thoughts of serialization crept in.
If you could help me get rid of the irrational unregistered_class exceptions I'll light a candle for your souls!
Update: Funny thing is, the exception isn't raised for all derived classes of Component.
After frying my neurones looking for an answer, I stumbled upon this line in the documentation:
Static Libraries and Serialization
Code for serialization of data types can be saved in libraries just as
it can for the rest of the type implementation. This works well, and
can save huge amount of compilation time. Only compile serialization
definitions in the library. Explicitly instantiate serialization code
for ALL archive classes you intend to use in the library. For exported
types, only use BOOST_CLASS_EXPORT_KEY in headers. For exported types,
only use BOOST_CLASS_EXPORT_IMPLEMENT in definitions compiled in the
library. For any particular type, there should be only one file which
contains BOOST_CLASS_EXPORT_IMPLEMENT for that type. This ensures that
only one copy of serialization code will exist within the program. It
avoids wasted space and the possibility of having different versions
of the serialization code in the same program. Including
BOOST_CLASS_EXPORT_IMPLEMENT in multiple files could result in a
failure to link due to duplicated symbols or the throwing of a runtime
exception.
Splitting BOOST_CLASS_EXPORT into BOOST_CLASS_EXPORT_KEY and BOOST_CLASS_EXPORT_IMPLEMENT seems to work.