C++:如何按值将对象从 unique_ptr 传递到函数中?

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

我想使用 github page 中的 cereal 库将 xml 加载到对象中。 到目前为止一切都很好。

但在我的应用程序中,它有点复杂:xml 文件需要 loaded/filled 的对象必须通过多态指针访问。 因此,如果 a 使用原始指针,cereal 库将拒绝接受它,并要求使用智能指针。 但是,当我将智能指针指向通用加载 cereal 函数(即 void serialize( Archive & ar )cereal::XMLInputArchive::operator() 相关)时,它会尝试加载到整个指针本身而不是指向对象。

这是一个 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

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>

和来自cereal.hppmake_nvp的签名:

template <class T> inline
 NameValuePair<T> make_nvp( std::string const & name, T && value )
{
  return {name.c_str(), std::forward<T>(value)};
}

上述MWE的确切错误信息是:

 terminate called after throwing an instance of 'cereal::Exception'
  what():  XML Parsing failed - provided NVP (polymorphic_id) not found
 Abandon

所以我的问题是:如何通过值将对象从 unique_ptr 传递到 cereal 函数? 或者有其他方法可以解决这个问题吗?

问题是您加载文件的方式与编写文件的方式不同。 如果您使用智能指针编写它,则该文件包含其他数据,包括一个节点polymorphic_id。您将拥有一个与此类似的文件:

<?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>

修改注册后使用

 CEREAL_REGISTER_TYPE_WITH_NAME(ClassRectangle, "Rectangle")

而不是:

 CEREAL_REGISTER_TYPE(ClassRectangle)

我用来写文件的代码是:

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

然后可以使用指向 SuperParentClass 的智能指针读回该文件。

或者,如果您想在智能指针中读取原始文件,可以使用以下几行轻松完成:

    std::unique_ptr<ClassRectangle> UPSPC(new ClassRectangle());
    arr(*UPSPC);
    UPSPC->run();

显然,您必须在此处使用派生类型,因为保存文件时没有处理多态类型所需的层次信息。

还要注意表达式 arr(*UPSPC) 中的 *。这样,您将加载到一个已分配的对象中。

更新

如果每种派生类型最多有一个对象,则可以按名称加载它们:

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