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
Related
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.
I haven't been able to find any docs on how to serialize a unique_ptr to an array. Any help would be great.
struct Counter{
int index;
unique_ptr<char []> name;
template<class Archive>
void serialize(Archive & archive){
archive(index, name ); // serialize things by passing them to the archive
}
};
How it is assigned.
auto buffer = std::unique_ptr<char[]>(new char[BUFFER_SIZE]);
instance.name = std::move(buffer);
You can do it, but it needs some extra work. It differs depending on the type of the archive.
For text-based archives (e.g., XMLOutputArchive/XMLInputArchive and JSONOutputArchive/JSONInputArchive) you can use the saveBinaryValue() / loadBinaryValue() (http://uscilab.github.io/cereal/assets/doxygen/classcereal_1_1JSONOutputArchive.html).
Here's a complete example:
#include <iostream>
#include <memory>
#include <cereal/archives/xml.hpp>
#include <cereal/cereal.hpp>
struct Counter
{
static constexpr std::size_t BUFFER_SIZE = 12;
int index{};
std::unique_ptr<char[]> name;
Counter() = default;
Counter(int i)
: index{i},
name{new char[BUFFER_SIZE]}
{}
template<class Archive>
void load(Archive& archive)
{
archive(index);
name.reset(new char[BUFFER_SIZE]);
archive.loadBinaryValue(name.get(), BUFFER_SIZE * sizeof(decltype(name[0])));
}
template<class Archive>
void save(Archive& archive) const
{
archive(index);
archive.saveBinaryValue(name.get(), BUFFER_SIZE * sizeof(decltype(name[0])));
}
};
int main()
{
cereal::XMLOutputArchive archive(std::cout);
Counter c(42);
archive(CEREAL_NVP(c));
}
If you are using BinaryOutputArchive / BinaryInputArchive or PortableBinaryOutputArchive / PortableBinaryInputArchive the functions become saveBinary() and loadBinary() (http://uscilab.github.io/cereal/assets/doxygen/classcereal_1_1PortableBinaryOutputArchive.html).
For those, you can also wrap your array using binary_data():
template<class Archive>
void save(Archive& archive) const
{
archive(index, cereal::binary_data(name.get(), BUFFER_SIZE));
}
I have a C++ / CLI project that uses boost serialization to serialize three different classes. I would like to know if it is possible to parse the first line of the boost serialization archive in order to know what class was serialized in this archive, and then create an object of the appropriate class and deserialize the archive into the object. That line would contain an ID (maybe a int or value of an enum class) to identify which class was serialized.
The file format is already handled by your choice of Archive implementation.
In practice that would be boost::archive::text_oarchive, boost::archive::binary_oarchive, boost::archive::xml_oarchive.
As long as your archive type itself doesn't vary, you can very easily use a Boost Variant to distinguish your payloads. In other words, make the serialization framework do the work for you, instead of "duct taping" around it:
Here's a demo that serializes 3 different (compound) payloads and roundtrips just fine without external knowledge of the payload actually there:
Live On Coliru
#include <boost/archive/text_oarchive.hpp>
#include <boost/archive/text_iarchive.hpp>
#include <boost/serialization/variant.hpp>
#include <boost/serialization/vector.hpp>
#include <boost/serialization/string.hpp>
#include <boost/serialization/access.hpp>
struct A {
int simple;
private:
friend class boost::serialization::access;
template <typename Ar> void serialize(Ar& ar, unsigned) {
ar & simple;
}
};
struct B {
std::string text;
private:
friend class boost::serialization::access;
template <typename Ar> void serialize(Ar& ar, unsigned) {
ar & text;
}
};
struct C {
A composed_a;
B composed_b;
private:
friend class boost::serialization::access;
template <typename Ar> void serialize(Ar& ar, unsigned) {
ar & composed_a & composed_b;
}
};
struct FileContents { // conventions...
boost::variant<A, B, C> payload;
private:
friend class boost::serialization::access;
template <typename Ar> void serialize(Ar& ar, unsigned) {
ar & payload;
}
};
#include <sstream>
#include <boost/lexical_cast.hpp>
//////////////////////////////////////////////////////////////////////////////////////////////////////////////
// For our roundtrip test, implement streaming as well so we can independently check equivalence
inline static std::ostream& operator<<(std::ostream& os, A const& v) {
return os << "A{" << v.simple << "}";
}
inline static std::ostream& operator<<(std::ostream& os, B const& v) {
return os << "B{" << v.text << "}";
}
inline static std::ostream& operator<<(std::ostream& os, C const& v) {
return os << "C{" << v.composed_a << ", " << v.composed_b << "}";
}
void roundtrip_test(FileContents const& original) {
std::stringstream ss;
{
boost::archive::text_oarchive oa(ss);
oa << original;
}
{
boost::archive::text_iarchive ia(ss);
FileContents clone;
ia >> clone;
std::string const before = boost::lexical_cast<std::string>(original.payload);
std::string const after = boost::lexical_cast<std::string>(clone.payload);
std::cout << "Roundtrip '" << before << "': " << std::boolalpha << (before == after) << "\n";
}
}
int main() {
roundtrip_test({ A { 42 } });
roundtrip_test({ B { "Life The Universe And Everything" } });
roundtrip_test({ C { {42}, { "Life The Universe And Everything" } } });
}
The output being:
Roundtrip 'A{42}': true
Roundtrip 'B{Life The Universe And Everything}': true
Roundtrip 'C{A{42}, B{Life The Universe And Everything}}': true
I would like to serialize struct A where I could save the tag name of the enum expressions instead of its integer in a non intruisive way (without having to change struct A).
enum e_fruit {
apple,
banana,
coconut
};
struct A {
e_fruit fruit;
int num;
};
namespace boost { namespace serialization {
template<class Archive>
void serialize(Archive & ar, A &a, const unsigned int version)
{
ar & boost::serialization::make_nvp("FruitType", a.fruit); // this will store an integer
ar & boost::serialization::make_nvp("Number", a.num);
}
}}
I have tried introducing a lookup table locally to the serialize function :
template<class Archive>
void serialize(Archive & ar, A &a, const unsigned int version)
{
static const char* const labels[] = { "Apple", "Banana", "Coconut" };
ar & boost::serialization::make_nvp("FruitType", labels[a.fruit]); // this won't work
ar & boost::serialization::make_nvp("Number", a.num);
}
Unfortunately I get the error :
Error 78 error C2228: left of '.serialize' must have
class/struct/union
since the prototype of make_nvp is
nvp< T > make_nvp(const char * name, T & t){
return nvp< T >(name, t);
}
so T should be a deduced template argument. I then thought about making a structure which would contains these labels but then I would have to add this in struct A which is what I want to avoid...
So how can we achieve this in the most non-intruisive way ?
I think you need to separate loading from saving. This compiles, I wonder it it is worth the game...
struct fruit_serializer
{
e_fruit &a_;
fruit_serializer(e_fruit &a) : a_(a) {}
template<class Archive>
void save(Archive & ar, const unsigned int version) const
{
std::string label = labels[static_cast<int>(a_)];
ar & boost::serialization::make_nvp("label", label);
}
template<class Archive>
void load(Archive & ar, const unsigned int version)
{
std::string label ;
ar & boost::serialization::make_nvp("label", label);
a_ = static_cast<e_fruit>(std::find(labels.begin(), labels.end(), label) - labels.begin());
}
BOOST_SERIALIZATION_SPLIT_MEMBER();
static std::vector<std::string> labels ;
};
std::vector<std::string> fruit_serializer::labels({ "Apple", "Banana", "Coconut" });
template<class Archive>
void serialize(Archive & ar, A &a, const unsigned int version)
{
fruit_serializer a1(a.fruit);
ar & boost::serialization::make_nvp("FruitType", a1);
}
As much as I hate to resurrect an old question, I wanted to do the same thing, but needed to make the enums string-serializable without any decorators. Since I didn't find much else regarding the topic, I'm posting my hacky solution as an option for anyone else who needs to serialize their enums as strings.
First off, some sample types to (eventually) serialize:
#include <iostream>
#include <sstream>
#include <boost/serialization/nvp.hpp>
#include <boost/archive/xml_oarchive.hpp>
// A few dummy enum types to test the solution
enum MyEnum00 {
Barb, Sarah, Lucy,
};
enum MyEnum01 {
Corey, Trevor, Jacob = 99,
};
const char* const to_cstring(const MyEnum01 e) {
switch (e) {
case Corey: return "Corey";
case Trevor: return "Trevor";
case Jacob: return "Jacob";
default: return "UNKNOWN";
}
}
inline std::ostream& operator<<(std::ostream& o, const MyEnum01 e) { return o << to_cstring(e); }
enum class MyEnumClass00 {
Ricky, Julian, Bubbles
};
enum class MyEnumClass01 {
Jim, Randy, Cyrus
};
const char* const to_cstring(const MyEnumClass01 e) {
switch (e) {
case MyEnumClass01::Jim: return "I let the liquor do the thinking, bud!";
case MyEnumClass01::Randy: return "Got any cheeeeeseburgers?";
case MyEnumClass01::Cyrus: return "I got work to do";
default: return "UNKNOWN";
}
}
inline std::ostream& operator<<(std::ostream& o, const MyEnumClass01 e) { return o << to_cstring(e); }
From boost_1_63_0/boost/archive/detail/oserializer.hpp, the function save_enum_type::invoke() is where enums get clobbered into ints.
Boost uses a somewhat clunky combination of templated structs with separately templated members, so it's difficult to make our change apply any time our desired enum types are used. As a workaround, we can specialize boost::archive::detail::save_enum_type for the archive type we're using. Then, we can overload its invoke() function to keep it from clobbering the enum types we need to have archived as strings.
save_enum_type::invoke is called roughly in the middle of boost's callstack, which eventually makes its way down into the basic_text_oprimitive class. There, an insertion operator is finally used to save the value to the ostream underlying the destination archive. We can take advantage of that implementation detail to get our enum types archived as strings by specializing the save_enum_type and impelmenting insertion operators for the target types.
namespace boost {
namespace archive {
namespace detail {
using xml_oarchive_ = boost::archive::xml_oarchive;
using save_non_pointer_type_ = detail::save_non_pointer_type<xml_oarchive_>;
template<>
struct save_enum_type<xml_oarchive_>
{
// This is boost's stock function that converts enums to ints before serializing them.
// We've added a copy to our specialized version of save_enum_type to maintain the exisitng behavior for any
// enum types we don't care to have archived in string form
template<class T>
static void invoke(xml_oarchive_& ar, const T& t) {
const int i = static_cast<int>(t);
ar << boost::serialization::make_nvp(NULL, i);
}
///// specialized enum types /////
// You could probably reduce all the repeated code with some type-trait magic...
static void invoke(xml_oarchive_& ar, const MyEnum00 &e) {
save_non_pointer_type_::invoke(ar, e);
}
static void invoke(xml_oarchive_& ar, const MyEnum01 &e) {
save_non_pointer_type_::invoke(ar, e);
}
// Won't work -- MyEnumClass00 doesn't have an insertion operator, so the underlying ostream won't know
// how to handle it
//static void invoke(xml_oarchive_& ar, const MyEnumClass00 &e) {
// save_non_pointer_type_::invoke(ar, e);
//}
static void invoke(xml_oarchive_& ar, const MyEnumClass01 &e) {
save_non_pointer_type_::invoke(ar, e);
}
};
} // namespace detail
} // namespace archive
} // namespace boost
And Finally some code to test everything:
int main()
{
std::stringstream outstream;
boost::archive::xml_oarchive ar(outstream);
MyEnum00 e00_0 = Barb;
MyEnum00 e00_1 = Sarah;
MyEnum00 e00_2 = Lucy;
MyEnum01 e01_0 = Corey;
MyEnum01 e01_1 = Trevor;
MyEnum01 e01_2 = Jacob;
MyEnumClass00 ec00_0 = MyEnumClass00::Ricky;
MyEnumClass00 ec00_1 = MyEnumClass00::Julian;
MyEnumClass00 ec00_2 = MyEnumClass00::Bubbles;
MyEnumClass01 ec01_0 = MyEnumClass01::Jim;
MyEnumClass01 ec01_1 = MyEnumClass01::Randy;
MyEnumClass01 ec01_2 = MyEnumClass01::Cyrus;
ar
// regular enums get passed down as int even if you don't explicitly convert them
<< BOOST_SERIALIZATION_NVP(e00_0)
<< BOOST_SERIALIZATION_NVP(e00_1)
<< BOOST_SERIALIZATION_NVP(e00_2)
// regular enums can also get special treatment
<< BOOST_SERIALIZATION_NVP(e01_0)
<< BOOST_SERIALIZATION_NVP(e01_1)
<< BOOST_SERIALIZATION_NVP(e01_2)
// enum classes that aren't specialized pass down as ints
<< BOOST_SERIALIZATION_NVP(ec00_0)
<< BOOST_SERIALIZATION_NVP(ec00_1)
<< BOOST_SERIALIZATION_NVP(ec00_2)
// enum classes can also get special treatment
<< BOOST_SERIALIZATION_NVP(ec01_0)
<< BOOST_SERIALIZATION_NVP(ec01_1)
<< BOOST_SERIALIZATION_NVP(ec01_2)
;
std::cout << outstream.str() << std::endl;
return 0;
}
I need the ability to save/read my data structures in my project, but all the data is in the form of quite complicated and distinct structures themselves which I typically implement through other structs and vectors. I have wrapped all of them up into a single struct so that I have something like
struct master{
std::vector<apprentice_type1> a;
std::vector<apprentice_type2> b; //etc.
std::string label;
};
with other ones defined like
struct apprentice_type1{
vec location;
int point_label;
std::vector<int> relational_data;
};
struct vec{
double x,y,z;
};
So it gets pretty complicated! I was desperately hoping something nice, quick and naive like
master obj;
//write to obj....
std::ofstream ofs("data.dat", std::ios::binary);
ofs.write((char *)&obj, sizeof(obj));
would work, but at present it doesn't seem to. Before I get lost in the debugging rabbit hole is this actually possible the way I'm approaching it or do I need to rethink? If so, how?
Thanks.
If you want an alternative to Boost serialization and have access to a C++11 compiler, you can also check out cereal. It works in a near identical fashion to Boost serialize but is a header only library so there is nothing to link against.
[...] or do I need to rethink? If so, how?
You will probably need to provide a full implementation (i.e. explore the "rabbit-hole").
This is a known problem (stream serialization) and there is no single best-approach solution to it, because most implementations need to solve different needs.
You can do one of the following:
implement std::i/ostream serialization; This means you will go over your classes and implement operator>>(std::istream*, your_type&) and it's inverse operator<<(std::ostream*, your_type&).
implement serialization based on a stream library (like boost.archive).
use a JSON or XML library.
use google protocol buffers (or something else)
roll your own implementation, depending on your needs.
If you go for boost::serialization, here is a little sample:
#include <fstream>
#include <vector>
#include <boost/archive/binary_iarchive.hpp>
#include <boost/archive/binary_oarchive.hpp>
#include <boost/serialization/vector.hpp>
template <typename T>
inline const boost::serialization::nvp<T> archive_value(const char* name, T& value) {
return boost::serialization::make_nvp(name, value);
}
const unsigned Version = 0;
class Point{
public:
double x,y,z;
private:
template <typename P, typename Archive>
static void serialize(P& p, Archive& ar, const unsigned int version) {
std::cout << "Point\n";
ar & archive_value("X", p.x);
ar & archive_value("Y", p.y);
ar & archive_value("Z", p.z);
}
public:
template <typename Archive>
void serialize(Archive& ar, const unsigned int version) const {
serialize(*this, ar, version);
}
template <typename Archive>
void serialize(Archive& ar, const unsigned int version) {
serialize(*this, ar, version);
}
};
BOOST_CLASS_VERSION(Point, Version)
struct Scene{
std::vector<Point> points;
private:
template <typename S, typename Archive>
static void serialize(S& s, Archive& ar, const unsigned int version) {
std::cout << "Scene\n";
ar & archive_value("Points", s.points);
}
public:
template <typename Archive>
void serialize(Archive& ar, const unsigned int version) const {
serialize(*this, ar, version);
}
template <typename Archive>
void serialize(Archive& ar, const unsigned int version) {
serialize(*this, ar, version);
}
};
BOOST_CLASS_VERSION(Scene, Version)
template <typename Archive>
void register_types(Archive& ar) {
ar.template register_type<Point>();
ar.template register_type<Scene>();
}
int main() {
Scene scene;
scene.points = { { 0, 0, 0 }, { 1, 1, 1 }, { 2, 2, 2 } };
// Output
{
std::ofstream out("test.dat", std::ios_base::binary);
boost::archive::binary_oarchive output(out);
// First the version!
output & archive_value("Version", Version);
// Next the types!
register_types(output);
// Finally the data
output & archive_value("Scene", scene);
}
scene.points = {};
// Input
{
int version;
std::ifstream in("test.dat", std::ios_base::binary);
boost::archive::binary_iarchive input(in);
// First the version!
input & archive_value("Version", Version);
// Next the types!
register_types(input);
// Finally the data
input & archive_value("Scene", scene);
}
for(const auto& p : scene.points)
std::cout << p.x << '\n';
}
Note: The file format may evolve and serialization functions (input and/or output) may get adjustment depending on the file version.