I use Boost to serialize classes I register with register_type, as described here.
Later, if I decide a specific class is no longer useful, and I want to open old file and discard the forgotten class instances, I don't have a way to do it.
How can I make this work?
Here is an example:
#include <iostream>
#include <sstream>
#include <boost/serialization/serialization.hpp>
#include <boost/serialization/vector.hpp>
#include <boost/archive/binary_iarchive.hpp>
#include <boost/archive/binary_oarchive.hpp>
struct Base
{
virtual ~Base() = default;
template <class Archive>
void serialize(Archive &ar, long int version)
{}
virtual void display(std::ostream &os) const = 0;
};
struct MyType1 : public Base
{
int i, j;
MyType1(): MyType1(0, 0) {}
MyType1(int i_, int j_): i {i_}, j {j_} {}
~MyType1() override = default;
template <class Archive>
void serialize(Archive &ar, long int version)
{
ar & boost::serialization::base_object<Base>(*this);
ar & i;
ar & j;
}
void display(std::ostream &os) const override
{
os << "MyType1{" << i << ", " << j << "}";
}
};
struct MyType2 : public Base
{
float a;
MyType2(): MyType2(0.f) {}
MyType2(float a_): a {a_} {}
~MyType2() override = default;
template <class Archive>
void serialize(Archive &ar, long int version)
{
ar & boost::serialization::base_object<Base>(*this);
ar & a;
}
void display(std::ostream &os) const override
{
os << "MyType2{" << a << '}';
}
};
std::ostream &operator<<(std::ostream &os, Base const &b)
{
b.display(os);
return os;
}
int main()
{
std::stringstream stream;
{
boost::archive::binary_oarchive oar {stream};
oar.register_type<MyType1>();
oar.register_type<MyType2>();
Base *foo1 = new MyType1 {42, 12},
*foo2 = new MyType2 {32.f};
oar << foo1 << foo2;
delete foo1;
delete foo2;
}
boost::archive::binary_iarchive iar {stream};
// Remove a type
//iar.register_type<MyType1>();
iar.register_type<MyType2>();
Base *obj = nullptr;
iar >> obj;
// Outputs MyType2{5.88545e-44}
std::cout << *obj << '\n';
return 0;
}
You can forget the type, but then you can obviously no longer read any archives containing the old type. Since that's exactly what you're trying to, it breaks.
Simplified example Live On Coliru
If your archive didn't contain the type, you would still get an exception, because of the way you are registering the types: Live On Coliru throwing an archive exception "unregistered class".
The way you register types means you have to match the order and number of registrations always.
Exporting Classes
Instead, consider using the export mechanism: https://www.boost.org/doc/libs/1_80_0/libs/serialization/doc/special.html#export
Here's the example adapted: Live On Coliru
This takes care of unique identification (using the qualified type name). Therefore, when you are ready to drop support for old archives with the old class, you can just omit it, and be happy: Live On Coliru
With
Prints
Advanced: Versioning
You can also use the explicit register-type, but you'd have to do versioning to get some kind of compatibility instead of UB.
You could wrap your archived data in a class that does the registration and is also versioned:
Live On Coliru: V0
Printing
Note how I took the opportunity to get rid of raw pointers.
Introducing V1 of MyArchiveData
We declare the new class version:
And implement the new logic:
See the results Live On Coliru: V1 vs V0
Testing with
Output:
SUMMARIZING
You will probably note that the advanced/object versioning approach requires you to keep the implementation of Type1 around. That's only partially true. You need it as long as you want to be able to consume the old archives.
You could use the techniques shown above to make a conversion tool that converts old archives to the new version.
Then when you're ready to drop the support for the old stuff and the corresponding class implementation you can drop the
MyType1definition altogether and remove the switch case for the old version(s).