c++unique-ptrcereal

C++: How to pass the object from a unique_ptr into a function by value?


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 ?


Solution

  • 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();