I would like to use the cereal
library from this github page to load xml
into objects.
Up to this point everything is fine.
But in my application, it is a bit more complex: the object that needs to be loaded/filled by the xml
file, have to be accessed through polymorphic pointer.
Therefore, if a use a raw pointer, the cereal
lib refuse to take it, and ask for a smart pointer.
But, when I give a smart pointer to the generic load cereal
function (ie void serialize( Archive & ar )
related to cereal::XMLInputArchive::operator()
), it tries to load into the whole pointer itself rather than the pointing object.
Here is an MWE:
#include <string.h>
#include <stdio.h>
#include <iostream>
#include <fstream>
#include <map>
#include <memory>
#include <cereal/archives/xml.hpp>
#include <cereal/types/memory.hpp>
#include <cereal/types/polymorphic.hpp>
using namespace std;
class SuperParentClass
{
public:
virtual std::string get_nameClass() =0;
virtual int run() = 0;
void serialize() ;
};
class ClassRectangle : public SuperParentClass
{
public:
std::string get_nameClass(){return "Rectangle";};
double length=0.;
double width=0.;
template <class Archive>
void serialize( Archive & ar )
{
ar( CEREAL_NVP( length ) );
ar( CEREAL_NVP( width ) );
}
int run()
{
std::cout << "I am a Rectangle with length "<<length<<" and width "<<width << std::endl;
return 0;
}
};
CEREAL_REGISTER_TYPE(ClassRectangle)
CEREAL_REGISTER_POLYMORPHIC_RELATION(SuperParentClass, ClassRectangle)
int main(void)
{
// Beginning of main.
cout << "(Start)" << endl;
// Loading from file Part.
{
cout << "Load " << endl;
std::ifstream is("input.xml");
cereal::XMLInputArchive arr(is);
ClassRectangle Rec;
arr( Rec ); // or // arr( cereal::make_nvp("Rectangle", Rec) );
Rec.run();
// Now, a bit more complex but closer to my application, because I need a polymorphic pointer !
std::unique_ptr<SuperParentClass> UPSPC(new ClassRectangle());
arr( cereal::make_nvp("Rectangle", UPSPC ) ); // ask for a polymorphic_id, it does not apply the right function
// arr( cereal::make_nvp("Rectangle", UPSPC.get() ) ); // exempt a smart pointer not a raw pointer
// arr( cereal::make_nvp("Rectangle", &UPSPC ) ); // exempt a smart pointer not a raw pointer
// arr( cereal::make_nvp("Rectangle", std::move(UPSPC) ) ); // ask for a polymorphic_id, it does not apply the right function
// arr( cereal::make_nvp("Rectangle", *UPSPC.get() ) ); // does not compile.
// arr( UPSPC ); // create an Segmentation Error, because cereal is looking for a polymorphic_id I guess.
UPSPC->run();
}
// End of the main.
cout << "(End) " << endl;
return 0;
}
// EoF
with input.xml
:
<?xml version="1.0" encoding="utf-8"?>
<cereal>
<Rectangle>
<length>2</length>
<width>11</width>
</Rectangle>
<Rectangle>
<length>12</length>
<width>10</width>
</Rectangle>
</cereal>
and the signature of make_nvp
from cereal.hpp
:
template <class T> inline
NameValuePair<T> make_nvp( std::string const & name, T && value )
{
return {name.c_str(), std::forward<T>(value)};
}
The exact error message of the above MWE is :
terminate called after throwing an instance of 'cereal::Exception'
what(): XML Parsing failed - provided NVP (polymorphic_id) not found
Abandon
So my question is: how can I pass the object, from a unique_ptr
, into the cereal
function by value ?
Or is there another way to work around this ?
The problem is that you don't load the file the same way you write it. If you write it using smart pointers, then the file contains additionnal data including a node polymorphic_id
. You would have a file similar to this one:
<?xml version="1.0" encoding="utf-8"?>
<cereal>
<value0>
<polymorphic_id>2147483649</polymorphic_id>
<polymorphic_name>Rectangle</polymorphic_name>
<ptr_wrapper>
<valid>1</valid>
<data>
<length>1</length>
<width>5</width>
</data>
</ptr_wrapper>
</value0>
<value1>
<polymorphic_id>1</polymorphic_id>
<ptr_wrapper>
<valid>1</valid>
<data>
<length>2</length>
<width>25</width>
</data>
</ptr_wrapper>
</value1>
</cereal>
after modifying the registration to use
CEREAL_REGISTER_TYPE_WITH_NAME(ClassRectangle, "Rectangle")
instead of:
CEREAL_REGISTER_TYPE(ClassRectangle)
The code I used to write the file is:
std::ofstream is("output.xml");
cereal::XMLOutputArchive arr(is);
auto *rp1 = new ClassRectangle();
rp1->length = 1;
rp1->width = 5;
std::unique_ptr<SuperParentClass> p1(rp1);
auto *rp2 = new ClassRectangle();
rp2->length = 2;
rp2->width = 25;
std::unique_ptr<SuperParentClass> p2(rp2);
arr(p1);
arr(p2);
Then that file can be read back using smart pointers to SuperParentClass
.
Alternatively, if you want to read the original file in a smart pointer, it can easily be done by using the following lines:
std::unique_ptr<ClassRectangle> UPSPC(new ClassRectangle());
arr(*UPSPC);
UPSPC->run();
Obviously, you have to use the derived type here because the file was saved without hierarchical information needed to handle polymorphic type.
Also notice the * in the expression arr(*UPSPC)
. That way, you are loading into an already allocated object.
Update
If you have at most one obect of each derived type, then you could load them by name:
std::unique_ptr<ClassTriangle> tri(new ClassTriangle());
std::unique_ptr<ClassRectangle> rect(new ClassRectangle());
arr(cereal::make_nvp("Triangle", *tri));
arr(cereal::make_nvp("Rectangle", *rect));
rect->run();
tri->run();