I am using boost to (de)serialize some classes. All is well except for a class which has astd::vector<Artefact*>
member (see below for definitions). I have problems with vectors of pointers.
I can (de)serialize single instances of class Artefact
perfectly.
I cannot serialize a class which has a vector of pointers to class Artefact
.
The boost error when I try to do so is:
"unregistered class - derived class not registered or exported"
I am getting some output. Archive file:
22 serialization::archive 19 1 0
0 1 0
1 1 0
2 0 0 66 0
#include <boost/serialization/vector.hpp>
#include <boost/serialization/map.hpp>
...
class Tag {
public:
bool tag = false;
};
class Artefact : public Tag {
public:
std::vector<enjyn_Vertex> vertices;
std::vector<unsigned int> indices;
std::vector<enjyn_Texture*> textures;
glm::vec3 position{ 0.0f };
glm::vec3 scale{ 1.0f };
glm::vec3 rotation{ 0.0f, 1.0f, 0.0f };
float rotation_degrees{ 0.0f };
glm::vec4 colour{ 1.0f, 1.0f, 1.0f, 1.0f };
...
std::vector<Artefact> artefacts; // note, no problem serializing std::vector<Artefact> here
...
};
//Mainly static data and function members
class State {
public:
...
static std::map<std::string, bool> state;
...
}
class Event {
public:
...
virtual void OnMouseFocus(); //Lots of virtual data members like this one here
..
}
//Large class with lots of data and function members.
class Enjyn : public Event, public State {
public:
...
std::vector<Artefact*> artefacts; // I believe this is the problem member
...
}
I have non-intrusive free serialize functions for all classes I want to archive including serialize functions for all base classes and non built in data members).
The serialize function below (de)serializes perfectly for single instances of Artefact:
#include <boost/serialization/export.hpp>
#include <boost/archive/text_oarchive.hpp>
#include <boost/archive/text_iarchive.hpp>
...
void serialize(Archive& ar, Artefact& a, const unsigned int version) {
//ar.template register_type<Artefact>(); ; commented as classes exported see below
ar& boost::serialization::base_object<enjyn_Tag>(a);
ar& a.vertices;
ar& a.indices;
ar& a.textures;
ar& a.position;
ar& a.scale;
ar& a.rotation;
ar& a.rotation_degrees;
ar& a.colour;
ar& a.aabb;
ar& a.bsphere;
ar& a.tex_offset_x;
ar& a.tex_offset_y;
ar& a.tex_scale_x;
ar& a.tex_scale_y;
ar& a.name;
ar& a.artefacts; //works fine for vector of non-pointer vector of Artefact.
}
However when I try to archive an object with a std::vector<Artefact*>
, I get:
"unregistered class - derived class not registered or exported"
void serialize(Archive& ar, Enjyn& e, const unsigned int version)
{
//ar.template register_type<enjyn>(); commented as classes exported see below:
ar& boost::serialization::base_object<Event>(e); // not sure whether this is necessary, no tracking requried of base classes.
ar& boost::serialization::base_object<State>(e);
ar.template register_type<Artefact>();
...
ar& e.artefacts; //This is a std::vector<Artefact*> which I cant archive
//if i remove this and add include any 'normal' non-pointer
//built in types the serialization appears to work perfectly.
...
}
I am serializing single instances of Artefact objects perfectly like this:
if (e.primary_artefact) { //primary_artefact is a pointer to an instance of Artefact
try {
std::string filepath = "test_filename";
std::ofstream o(filepath);
{
boost::archive::text_oarchive oa(o);
oa << *e.primary_artefact;
}
}
catch (std::ifstream::failure e) {
std::cerr << "ERROR:IFSTREAM: " << e.what() << std::endl;
}
catch (boost::archive::archive_exception e) {
std::cerr << "ERROR:BOOST SERIALISATION: " << e.what() << std::endl;
}
}
Attempting to serialize class Enjyn using this function creates error if I include Enjyn.artefacts
(std::vector<Artefact*>) in the serialize function:
void serialize_enjyn(enjyn& e, std::string& name)
try {
std::string filepath = name;
std::ofstream o(filepath);
{
boost::archive::text_oarchive oa(o);
oa << e;
}
}
catch (std::ifstream::failure e) {
std::cerr << "ERROR:IFSTREAM: " << e.what() << std::endl;
}
catch (boost::archive::archive_exception e) {
std::cerr << "ERROR:BOOST SERIALISATION: " << e.what() << std::endl;
}
}
I went mad and exported GUIDS for all types in my main program file so all relevant types could be tracked within the archive. Not that this is particularly necessary. Only tracking of '''class Artefact''' is required.
#include <boost/serialization/export.hpp>
BOOST_CLASS_EXPORT_GUID(glm::vec2, "vec2")
BOOST_CLASS_EXPORT_GUID(glm::vec3, "vec3")
BOOST_CLASS_EXPORT_GUID(glm::vec4, "vec4")
BOOST_CLASS_EXPORT_GUID(Artefact, "enjyn_Artefact")
BOOST_CLASS_EXPORT_GUID(Vertex, "enjyn_Vertex")
BOOST_CLASS_EXPORT_GUID(Texture, "enjyn_Texture")
BOOST_CLASS_EXPORT_GUID(Tag, "enjyn_Tag")
BOOST_CLASS_EXPORT_GUID(State, "enjyn_State")
BOOST_CLASS_EXPORT_GUID(Event, "enjyn_event")
BOOST_CLASS_EXPORT_GUID(Enjyn, "enjyn")
int main(int argc, char* argv[]){
Note, the Artefact object is used as a base for other classes. I have not registered those classes or written serialization functions for them as Artefact has no virtual functions, I am trying to serialize Artefact directly (though indirectly also via pointers), and the documentation for registration states:
It turns out that the kind of object serialized depends upon whether the base class (base in this case) is polymophic or not. If base is not polymorphic, that is if it has no virtual functions, then an object of the type base will be serialized. Information in any derived classes will be lost. If this is what is desired (it usually isn't) then no other effort is required.
The problem appears to be with the member std::vector<Artefact*> artefact
in Enjyn. When I remove this from the serialize function for Enjyn, Enjyn serializes perfectly. I am obviously missing something fundemental to do with:
pointers, std::vector, and derived types and base classes from the boost documentation.
Please help!
If you actually took the time to make that a correct, self-contained example, you may find that there is no problem:
#include <boost/serialization/map.hpp>
#include <boost/serialization/vector.hpp>
#include <boost/serialization/array.hpp>
#include <iostream>
// mocking stuff
namespace glm {
struct vec3 {
std::array<float, 3> a = {0,0,0};
bool operator==(vec3 const& rhs) const = default;
};
struct vec4 {
std::array<float, 4> a = {0,0,0,0};
bool operator==(vec4 const& rhs) const =default;
};
} // namespace glm
struct enjyn_Vertex {
bool operator==(enjyn_Vertex const&) const = default;
};
struct enjyn_Texture {
bool operator==(enjyn_Texture const&) const = default;
};
struct enjyn_Tag {
bool tag = false;
bool operator==(enjyn_Tag const&) const = default;
};
class Artefact : public enjyn_Tag {
public:
std::vector<enjyn_Vertex> vertices;
std::vector<unsigned int> indices;
std::vector<enjyn_Texture*> textures; // not owned, so leaked?
glm::vec3 position{0.0f};
glm::vec3 scale{1.0f};
glm::vec3 rotation{0.0f, 1.0f, 0.0f};
float rotation_degrees{0.0f};
glm::vec4 colour{1.0f, 1.0f, 1.0f, 1.0f};
std::vector<Artefact> artefacts;
bool operator==(Artefact const& rhs) const {
if (!enjyn_Tag::operator==(rhs))
return false;
auto tie = [](Artefact const& a) {
return std::tie(a.vertices, a.indices, /*a.textures,*/ a.position,
a.scale, a.rotation, a.rotation_degrees, a.colour,
a.artefacts);
};
if (tie(*this) != tie(rhs))
return false;
// compare textures indirectly
if (textures.size() != rhs.textures.size())
return false;
for (size_t i = 0; i<textures.size(); ++i)
if (*textures[i] != *rhs.textures[i])
return false;
return true;
}
private:
};
//Mainly static data and function members
class State {
public:
static std::map<std::string, bool> state;
bool operator==(State const&) const = default;
};
/*static*/ std::map<std::string, bool> State::state;
class Event {
public:
virtual void OnMouseFocus() {
} // Lots of virtual data members like this one here
bool operator==(Event const&) const = default;
};
//Large class with lots of data and function members.
class Enjyn
: public Event
, public State {
public:
// Rule Of Three...
~Enjyn() { for (auto a : artefacts) delete a; }
Enjyn() = default;
Enjyn(Enjyn const&) = delete;
Enjyn(Enjyn&&) = default;
Enjyn& operator=(Enjyn const&) = delete;
std::vector<Artefact*> artefacts;
bool operator==(Enjyn const& rhs) const {
if (! Event::operator==(rhs)) return false;
if (! State::operator==(rhs)) return false;
// compare artefacts indirectly
if (artefacts.size() != rhs.artefacts.size())
return false;
for (size_t i = 0; i<artefacts.size(); ++i)
if (*artefacts[i] != *rhs.artefacts[i])
return false;
return true;
}
};
#include <boost/serialization/export.hpp>
#include <boost/archive/text_oarchive.hpp>
#include <boost/archive/text_iarchive.hpp>
namespace boost::serialization {
template <typename Archive> void serialize(Archive& ar, enjyn_Vertex&, unsigned) { }
template <typename Archive> void serialize(Archive& ar, enjyn_Texture&, unsigned) { }
template <typename Archive> void serialize(Archive& ar, enjyn_Tag& t, unsigned) { ar& t.tag; }
template <typename Archive> void serialize(Archive& ar, glm::vec3&v, unsigned) { ar& v.a; }
template <typename Archive> void serialize(Archive& ar, glm::vec4&v, unsigned) { ar& v.a; }
template <typename Archive> void serialize(Archive& ar, Artefact& a, unsigned)
{
//ar.template register_type<Artefact>(); ; commented as classes exported see below
ar& boost::serialization::base_object<enjyn_Tag>(a);
ar& a.vertices;
ar& a.indices;
ar& a.textures;
ar& a.position;
ar& a.scale;
ar& a.rotation;
ar& a.rotation_degrees;
ar& a.colour;
//ar& a.aabb;
//ar& a.bsphere;
//ar& a.tex_offset_x;
//ar& a.tex_offset_y;
//ar& a.tex_scale_x;
//ar& a.tex_scale_y;
//ar& a.name;
ar& a.artefacts; // works fine for vector of non-pointer vector of Artefact.
}
template <typename Archive> void serialize(Archive& ar, Event& e, unsigned)
{
}
template <typename Archive> void serialize(Archive& ar, Enjyn& e, unsigned)
{
ar& e.state; // warning: serializing statics may not do what you want
ar& e.artefacts;
}
} // namespace boost
Enjyn bake_me_one_with_everything();
int main() {
Enjyn const original = bake_me_one_with_everything();
std::stringstream ss;
{
boost::archive::text_oarchive oa(ss);
oa << original;
}
std::cout << ss.str() << "\n";
{
boost::archive::text_iarchive ia(ss);
Enjyn e;
ia >> e;
std::cout << "Roundtrip equal: " << std::boolalpha << (e == original)
<< std::endl;
}
}
#include <random>
Enjyn bake_me_one_with_everything()
{
Enjyn e;
std::mt19937 urbg{std::random_device{}()};
auto frand =
std::bind(std::uniform_real_distribution<float>{}, std::ref(urbg));
auto r10 =
std::bind(std::uniform_int_distribution<>{1, 10}, std::ref(urbg));
for (int i = 0; i < r10(); ++i) { // very sloppy loop
auto& a = *e.artefacts.emplace_back(new Artefact());
a.artefacts.resize(r10());
a.colour = {frand(), frand(), frand()};
a.position = {{frand(), frand(), frand()}};
a.rotation = {{frand(), frand(), frand()}};
a.rotation_degrees = frand() * 360;
a.scale = {{frand(), frand(), frand()}};
a.tag = r10() % 2;
std::generate_n(back_inserter(a.indices), r10(), r10);
a.vertices.assign(r10(), enjyn_Vertex{});
a.textures.assign(r10(), new enjyn_Texture{});
}
return e;
}
Prints things like
Your princess is in another castle.
I notice a lot of "gratuitous" use of inheritance (e.g. to inherit static data members), but specifically you inherit Enjyn
from Event
, which is a polymorphic type.
So my best guess is that this contains your actual issue.
Otherwise, you should be able to go from this live sample.
Your code presentation shows some ordering that indicates you may be separating code across source files. In that case, don't fall into the trap of registering your types in a file that doesn't include all the relevant archive types BEFORE the export defitions: docs
BOOST_CLASS_EXPORT
in the same source module that includes any of the archive class headers will instantiate code required to serialize polymorphic pointers of the indicated type to the all those archive classes. If no archive class headers are included, then no code will be instantiated.