С++: как передать объект из unique_ptr в функцию по значению?

avatar
R. N
8 августа 2021 в 19:31
290
1
0

Я хотел бы использовать библиотеку cereal с этой страницы github для загрузки xml в объекты. До этого момента все нормально.

Но в моем приложении все немного сложнее: объект, который нужно загрузить/заполнить файлом xml, должен быть доступен через полиморфный указатель. Поэтому, если используется необработанный указатель, библиотека 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>

и подпись make_nvp от cereal.hpp:

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 по значению? Или есть другой способ обойти это?

Источник
HTNW
8 августа 2021 в 19:39
2

1) Пожалуйста, уменьшите это до MCRE. В настоящее время этот вопрос содержит много нерелевантных деталей и отсутствует действительно важная деталь о том, что такое подпись cereal::make_nvp. 2) make_nvp не принимает по значению; он принимает по ссылке. 3) Интеллектуальные указатели должны иметь синтаксис, аналогичный обычным указателям при доступе к объекту (объектам), на который указывает. Как вы обычно получаете значение из указателя?

Paul Sanders
8 августа 2021 в 19:57
1

Может arr( cereal::make_nvp("Rectangle", *UPSPC.get() ) );?

R. N
8 августа 2021 в 20:31
0

@PaulSanders, он возвращает ошибку компиляции error: static assertion failed: cereal could not find any input serialization functions for the provided type and archive combination.

R. N
8 августа 2021 в 20:40
0

@HTNW спасибо за предложения по улучшению. 1) Я постараюсь уменьшить его настолько, насколько смогу. 2) Хорошо, мой плохой. Но разве это что-то здесь меняет? 3) используя *, но в данном случае мне это не помогает.

Phil1970
8 августа 2021 в 20:50
2

Почему бы не прочитать документацию библиотеки: uscilab.github.io/cereal/polymorphism.html? Если вы прочтете его сами, то нам не придется читать его только для того, чтобы ответить вам.

R. N
8 августа 2021 в 21:07
0

@ Phil1970 Я прочитал это, так что, возможно, я что-то пропустил, поэтому вы можете мне помочь. В противном случае я предпринял несколько попыток, используя веб-страницу, на которую вы указываете. А я со своей нах, с моей проблемой не стыкуется. Например, если вы попытаетесь дважды загрузить один и тот же класс, но не один и тот же объект (с разными атрибутами, как в моем примере), вы поймете, что атрибуты загружаются неправильно. Так что извините, если я что-то упустил, но если вы знаете, как помочь мне из своего опыта или документации, пожалуйста, сделайте это.

JaMiT
8 августа 2021 в 22:23
1

Может быть полезно дополнить "общую функцию загрузки хлопьев" именем этой функции. (Даже после прочтения вашего кода я не могу сказать, имеете ли вы в виду cereal::XMLInputArchive::operator() или cereal::make_nvp().

JaMiT
8 августа 2021 в 22:26
1

Вы должны дать точное сообщение об ошибке, а не свою интерпретацию. Если я достаточно понял из того, что вы написали, то сообщения об ошибке для строки, которая не комментируется, может быть достаточно.

Phil1970
8 августа 2021 в 22:26
1

@R.N В документации объясняются этапы регистрации, которых нет в вашем коде. Библиотека не сможет угадать иерархию классов, если вы не добавите строки типа CEREAL_REGISTER_POLYMORPHIC_RELATION(BaseClass, DerivedClassOne). У меня нет опыта работы с хлопьями, но из документации очень легко увидеть, что ваш код неполный.

R. N
8 августа 2021 в 23:02
0

@ Phil1970 Да, в документации упоминаются шаги, которых нет в моем коде, потому что я обнаружил, что они не имеют отношения к моей проблеме. Но поскольку вы пытаетесь мне помочь (я очень признателен), я редактирую свой вопрос, чтобы включить полиморфную инструкцию, требуемую cereal (надеюсь, это поможет вам в ответ). Это не меняет сообщение об ошибке, которое я получаю. Потому что структура входного файла действительно отличается в полиморфном случае, указанном в документации: он хранит и загружает текущее состояние указателя. Я пытаюсь загрузить цель объекта по указателю.

R. N
8 августа 2021 в 23:15
0

@JaMiT Я отредактировал свой вопрос, включив точное сообщение об ошибке, которое я получаю при выполнении. (Извините, я должен был сделать это с самого начала). И я попытался уточнить общую функцию загрузки. Я имею в виду саму функцию serialize, используемую cereal для сохранения и загрузки нескольких форматов выходных файлов. Так что это связано с cereal::XMLInputArchive::operator(). На самом деле все cereal::make_nvp() здесь только для помощи в отладке, так как они дают имена переменным. Я тоже пробовал без, разницы никакой.

Phil1970
9 августа 2021 в 00:34
0

Был ли XML-файл сгенерирован хлопьями, или вы написали произвольный XML-файл, надеясь, что он сработает? Вы уверены, что допустимо иметь 2 узла на одном уровне с одинаковым именем (<Rectangle> в вашем случае)? Я не видел этого в документации. Обычно должно быть проще начать писать код, который сначала сохраняет данные. Кроме того, обычно, если вы получаете исключение, вам должно быть намного проще понять проблему с помощью отладчика.

R. N
9 августа 2021 в 09:50
0

@Phil1970 Phil1970 В первую очередь я создал XML-файл с хлопьями. И я пытаюсь сейчас перезагрузить этот сгенерированный файл. Кроме того, вполне допустимо иметь 2 узла на одном уровне с одинаковым именем. Если хотите убедиться сами, скопируйте и вставьте часть моего примера ClassRectangle Rec; arr( Rec ); Rec.run(); еще раз и прокомментируйте конец.

Phil1970
9 августа 2021 в 23:53
0

Вы уже задавали подобный вопрос несколько дней назад. Таким образом, вы тратите время людей на этот вопрос, поскольку другой вопрос показывает, что вы уже знали, что это ограничение библиотеки: coderhelper.com/questions/68655182/…. А также выпуск на GitHub: github.com/USCiLab/cereal/issues/709.

R. N
10 августа 2021 в 08:43
0

@ Phil1970 это не похожий вопрос. В предыдущем вопросе я прошу способ узнать polymorphic_id заранее, а здесь я пытаюсь передать объект, на который указывает указатель, функции, чтобы функция никогда не увидела указатель. Два вопроса используют одну и ту же библиотеку, это правда, но для меня они совершенно разные. Я определенно не хочу тратить время людей.

Ответы (1)

avatar
Phil1970
9 августа 2021 в 14:31
1

Проблема в том, что вы загружаете файл не так, как пишете. Если вы записываете его с помощью интеллектуальных указателей, то файл содержит дополнительные данные, включая узел 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();
R. N
9 августа 2021 в 21:01
0

Ваше решение работает на бумаге, но не решает мою проблему. 1) вы используете std::unique_ptr<ClassRectangle> UPSPC вместо std::unique_ptr<SuperParentClass> UPSPC. Это важно, так как в моей реальной задаче я должен использовать родительский класс по другим причинам. 2) Я не знаю, как заранее получить polymorphic_id, используемый во входном файле. Вы получили его в ходе спасательной операции. Но я загружаю свое реальное приложение для получения информации для пользователей, поэтому я не могу знать соответствующую операцию сохранения (например, polymorphic_id), я просто знаю имя объекта (прямоугольник или другое).

Phil1970
9 августа 2021 в 22:46
0

Тогда вы, вероятно, используете неправильную библиотеку для работы... Из документации и некоторых испытаний ясно, что cereal предполагает определенный формат для полиморфной загрузки. Информация об имени узла, по-видимому, недоступна непосредственно пользователю библиотеки. Библиотека архивации, такая как cereal, предназначена для конкретной цели сохранения и загрузки данных с версиями. Возможно, вам нужна общая библиотека XML.

Phil1970
9 августа 2021 в 23:22
0

Я обнаружил, что если у вас есть не более одного объекта каждого производного класса, вы можете загрузить их все в определенные интеллектуальные указатели. Если ни один из подходов не работает для вас, то вам, вероятно, следует вместо этого использовать общую библиотеку (а затем удалить или переименовать вводящий в заблуждение узел cereal, поскольку вы больше не следуете этому формату).

R. N
10 августа 2021 в 08:36
0

Хорошим началом может быть наличие одного объекта каждого производного класса и загрузка их в интеллектуальный указатель. Но проблема заключается в «конкретном» умном указателе (т.е. std::unique_ptr<ClassRectangle> против std::unique_ptr<SuperParentClass>)... Итак, если мне придется переключиться на общую библиотеку XML, есть ли у вас рекомендации? В противном случае мои исходные идеи заключались в том, чтобы передать объект, на который нацелен умный указатель, функции, поэтому функция не будет запрашивать polymorphic_id, поскольку это будет объект, а не указатель. Или перегрузить функцию хлопьев для указателей. Но в обоих случаях я не знаю, как это сделать.