сериализация злаков и полиморфизм

Итак, я столкнулся с проблемой в С++ 11 с хлопьями (http://uscilab.github.io/cereal/).

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

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

Атрибут.hpp/cpp

class Attribute {
...

template<class Archive> void serialize(Archive&)
{   
}   

friend class cereal::access;
...

CEREAL_REGISTER_TYPE(mgraph::Attribute)

Идабле.hpp/cpp

class Idable {
...

Id id;

template<class Archive> void serialize(Archive& archive)
{
    archive(cereal::make_nvp("id", id)); 
}

template<class Archive> static void load_and_construct(Archive& ar, cereal::construct<mcommon::Idable>& construct)
{
    mcommon::Id id;
    ar(id);
    construct(id);
}

friend class cereal::access;
...

CEREAL_REGISTER_TYPE(mcommon::Idable)

Позиция.hpp/cpp

class Position
: public mgraph::Attribute
  , public mcommon::Displayable {

template<class Archive> void serialize(Archive& archive)
{   
    archive(cereal::make_nvp("Attribute",
                             cereal::base_class<mgraph::Attribute>(this)));
}   

friend class cereal::access;
...

CEREAL_REGISTER_TYPE(mfin::Position)

Аккаунт.hpp/cpp

class Account
: public mcommon::Idable
  , public Position {
...
Currency balance;

template<class Archive> void serialize(Archive& archive)
{   
    archive(cereal::make_nvp("Idable",
                             cereal::base_class<mcommon::Idable>(this)),
            cereal::make_nvp("Position",
                             cereal::base_class<mfin::Position>(this)),
            cereal::make_nvp("balance", balance));
}

template<class Archive> static void load_and_construct(Archive& ar, cereal::construct<Account>& construct)
{
    mcommon::Id iden;
    Currency::Code code;
    ar(iden, code);
    construct(iden, code);
}

friend class cereal::access;
...

CEREAL_REGISTER_TYPE(mfin::Account)

Итак, проблема возникает при сериализации mfin::Account. mfin::Account принадлежит std::list>. Когда мы переходим к функции сериализации для Idable, объект недействителен.

Заходя в gdb, который останавливается из-за segfault, я поднимаюсь на несколько кадров стека до этой строки: /usr/include/cereal/types/polymorphic.hpp:341. Который:

(gdb) list
336 
337     auto binding = bindingMap.find(std::type_index(ptrinfo));
338     if(binding == bindingMap.end())
339       UNREGISTERED_POLYMORPHIC_EXCEPTION(save, cereal::util::demangle(ptrinfo.name()))
340 
341     binding->second.shared_ptr(&ar, ptr.get());
342   }
343 
344   //! Loading std::shared_ptr for polymorphic types
345   template <class Archive, class T> inline

Вот что такое ptr:

(gdb) print *((mfin::Account*)(ptr.get()))
$10 = {<mcommon::Idable> = {_vptr.Idable = 0x4f0d50 <vtable for mfin::Account+16>, id = "bank"}, <mfin::Position> = {<mgraph::Attribute> = {
      _vptr.Attribute = 0x4f0d78 <vtable for mfin::Account+56>}, <mcommon::Displayable> = {_vptr.Displayable = 0x4f0da0 <vtable for mfin::Account+96>}, <No data fields>}, balance = {<mcommon::Displayable> = {
      _vptr.Displayable = 0x4f0570 <vtable for mfin::Currency+16>}, amount = 0, code = mfin::Currency::USD}}
(gdb) print ptr
$11 = std::shared_ptr (count 3, weak 0) 0x758ad0

Все выглядит хорошо. Но обратите внимание, когда я бросаю его в пустоту*:

$11 = std::shared_ptr (count 3, weak 0) 0x758ad0
(gdb) print *((mfin::Account*)((void*)ptr.get()))
$12 = {<mcommon::Idable> = {_vptr.Idable = 0x4f0d78 <vtable for mfin::Account+56>, 
    id = "\363aL\000\000\000\000\000PbL\000\000\000\000\000\304\031L\000\000\000\000\000\021#L", '\000' <repeats 13 times>, " \232N", '\000' <repeats 21 times>, "P\251@\000\000\000\000\000\370\377\377\377\377\377\377\377 \232N", '\000' <repeats 21 times>, "\304\031L\000\000\000\000\000P\251@", '\000' <repeats 45 times>, "St19_Sp_counted_deleterIPN4mfin7AccountE"...}, <mfin::Position> = {<mgraph::Attribute> = {
      _vptr.Attribute = 0x4f0570 <vtable for mfin::Currency+16>}, <mcommon::Displayable> = {_vptr.Displayable = 0x0}, <No data fields>}, balance = {<mcommon::Displayable> = {_vptr.Displayable = 0x0}, amount = 49, 
    code = (unknown: 7702648)}}

Это, конечно, то, что происходит в binding->second.shared_ptr (см. ниже), который принимает const void*.

(gdb) list
295             writeMetadata(ar);
296 
297             #ifdef _MSC_VER
298             savePolymorphicSharedPtr( ar, dptr, ::cereal::traits::has_shared_from_this<T>::type() ); // MSVC doesn't like typename here
299             #else // not _MSC_VER
300             savePolymorphicSharedPtr( ar, dptr, typename ::cereal::traits::has_shared_from_this<T>::type() );
301             #endif // _MSC_VER
302           };
303 
304         serializers.unique_ptr =

Что неправильного в моем использовании хлопьев, что может вызвать это? Вот последняя ошибка, которую я получаю:

Program received signal SIGSEGV, Segmentation fault.
0x000000000040f7cd in rapidjson::Writer<rapidjson::GenericWriteStream, rapidjson::UTF8<char>, rapidjson::MemoryPoolAllocator<rapidjson::CrtAllocator> >::WriteString (this=0x7fffffffd358, 
    str=0x4f1ae0 <vtable for mfin::Account+96> "\363aL", length=4989722) at /usr/include/cereal/external/rapidjson/writer.h:276
276             if ((sizeof(Ch) == 1 || characterOk(*p)) && escape[(unsigned char)*p])  {
Missing separate debuginfos, use: debuginfo-install boost-date-time-1.55.0-8.fc21.x86_64 boost-filesystem-1.55.0-8.fc21.x86_64 boost-program-options-1.55.0-8.fc21.x86_64 boost-system-1.55.0-8.fc21.x86_64 boost-thread-1.55.0-8.fc21.x86_64 fcgi-2.4.0-24.fc21.x86_64 glog-0.3.3-3.128tech.x86_64 libgcc-4.9.2-1.fc21.x86_64 libstdc++-4.9.2-1.fc21.x86_64

person David Mokon Bond    schedule 28.04.2015    source источник
comment
К вашему сведению, зерновые — это еда на основе зерна, которую обычно едят на завтрак. Последовательный — это процесс передачи одной информации за раз, например битов на жесткий диск.   -  person Thomas Matthews    schedule 28.04.2015
comment
это также имя библиотеки, uscilab.github.io/cereal, спасибо   -  person David Mokon Bond    schedule 28.04.2015
comment
Спасибо! .Теперь я жажду Froot Loops!   -  person Captain Obvlious    schedule 28.04.2015
comment
Я уверен, что это связано с приведением к vtable: stackoverflow.com/questions/9138730/ Но я пытаюсь понять, моя ли это ошибка или проблема с библиотекой.   -  person David Mokon Bond    schedule 29.04.2015


Ответы (3)


Хорошо, после долгих исследований я считаю, что у меня есть ответ на мою проблему. И я считаю, что это ошибка в библиотеке. После того, как я подтвержу это владельцам библиотек, я позабочусь о том, чтобы это соответствовало последним результатам.

Я создал простую программу ниже, которая демонстрирует эту проблему. Проблема связана с множественным наследованием, полиморфизмом и приведением типов. В приведенной ниже программе мы создали производный объект. Объект Derived при размещении в памяти будет иметь формат примерно.:

Derived:
  Base2::vtable
  Base2::var
  Base::vtable

Учитывать:

(gdb) print ptr
$2 = std::shared_ptr (count 1, weak 0) 0x63c580
(gdb) print *ptr
$3 = (Derived &) @0x63c580: {<Base2> = {_vptr.Base2 = 0x421f90 <vtable for Derived+16>, var = ""}, <Base> = {_vptr.Base = 0x421fa8 <vtable for Derived+40>}, <No data fields>}

Теперь, когда мы dynamic_pointer_cast приводим его к базе, мы имеем:

(gdb) print ptr
$8 = std::shared_ptr (count 2, weak 0) 0x63c590
(gdb) print *ptr
$9 = (Base &) @0x63c590: {_vptr.Base = 0x421fa8 <vtable for Derived+40>}

Здесь начинается проблема. Теперь в /usr/include/cereal/types/polymorphic.hpp, строка 341. У нас есть этот указатель на базу. Здесь у нас есть:

binding->second.shared_ptr(&ar, ptr.get());

Что заканчивается приведением к const void*. Однако позже на основе информации о типе мы приводим этот тип к зарегистрированному полиморфному типу. Поскольку shared_ptr указывает на объект типа Derived, это означает Derived*. Как показано ниже:

272       static inline void savePolymorphicSharedPtr( Archive & ar, void const * dptr, std::false_type /* has_shared_from_this */ )
273       {
274         PolymorphicSharedPointerWrapper psptr( dptr );
275         ar( CEREAL_NVP_("ptr_wrapper", memory_detail::make_ptr_wrapper( psptr() ) ) );
276       }

Теперь это означает, что ptr стека, который является Base*, был приведен к void*, а затем приведен к Derived*. Таким образом, цепочка приведения приводит к недопустимому объекту. Как видно ниже, ptr теперь недействителен:

(gdb) print *ptr
$7 = (const Derived &) @0x63c590: {<Base2> = {_vptr.Base2 = 0x421fa8 <vtable for Derived+40>, var = <error reading variable: Cannot access memory at address 0x49>}, <Base> = {_vptr.Base = 0x0}, <No data fields>}

Указатель указывает на vtable для Base, а не на Derived/Base2, как должно быть, поэтому программа вылетает:

{
    "ptr": {
        "polymorphic_id": 2147483649,
        "polymorphic_name": "Derived",
        "ptr_wrapper": {
            "id": 2147483649,
            "data": {
                "Base2": {

Program received signal SIGSEGV, Segmentation fault.
0x00007ffff7b8e9e3 in std::string::size() const () from /lib64/libstdc++.so.6

Ниже приведен пример программы, которая воспроизводит это:

// g++ test.cpp -std=c++11 -ggdb -o test && gdb ./test
#include <cereal/archives/json.hpp>
#include <cereal/types/polymorphic.hpp>
#include <iostream>

struct Base {
    virtual void foo() { } 
    template<class Archive> void serialize(Archive& archive) { } 
};

struct Base2 {
    virtual void foo() { } 
    std::string var;
    template<class Archive> void serialize(Archive& archive) {   
        archive(cereal::make_nvp("var", var));
    }   
};

struct Derived : public Base2, public Base {
    template<class Archive> void serialize(Archive& archive) {   
        archive(cereal::make_nvp("Base2",
                                 cereal::base_class<Base2>(this)),
                cereal::make_nvp("Base",
                                 cereal::base_class<Base>(this)));
    }   
};

CEREAL_REGISTER_TYPE(Base);
CEREAL_REGISTER_TYPE(Base2);
CEREAL_REGISTER_TYPE(Derived);

int main() {
    auto ptr = std::make_shared<Derived>();
    cereal::JSONOutputArchive ar(std::cout);
    ar(cereal::make_nvp("ptr", std::dynamic_pointer_cast<Base>(ptr)));

    return 0;
}
person David Mokon Bond    schedule 29.04.2015

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

#define SC_REGISTER_INPUT_ARCHIVE(Archive) \
namespace cereal    \
{   \
    namespace detail    \
    {   \
        template StaticObject<InputBindingCreator<Archive, first_polymorphic_class>>;   \
        template StaticObject<InputBindingCreator<Archive, second_polymorphic_class>>;  \
... /* repeat for all polymorphic serialized types */
    }   \
}

SC_REGISTER_INPUT_ARCHIVE(XMLInputArchive);
SC_REGISTER_INPUT_ARCHIVE(BinaryInputArchive);
person Vladimir Shutow    schedule 10.05.2017
comment
Это была ошибка в библиотеке, которая теперь исправлена: github.com/USCiLab/cereal/issues. /188 - person David Mokon Bond; 11.05.2017
comment
Нет, он все еще существует, так как я использую более поздний источник (1.2.2). Возможно, это связано с тем, что в моей настройке реализация сериализованного полиморфного класса находится в одной библиотеке, а создание экземпляров архивов — в другой библиотеке. Но включение приведенного выше кода приводит к принудительной инициализации последней библиотеки. - person Vladimir Shutow; 11.05.2017
comment
да, но я хочу сказать, что это не то же самое, что вопрос - person David Mokon Bond; 11.05.2017

Это не ответ на ваш конкретный вопрос, но он очень похож. Я пытался сериализовать структуру с помощью внешней функции сериализации, но получал ошибку о том, что моя производная структура не может быть преобразована в базовый тип структуры. Хитрость заключалась в том, что я не передавал указатель.

So

ar(cereal::base_class<Base>(derived_instance), derived_instance.y)

стал

ar(cereal::base_class<Base>(&derived_instance), derived_instance.y)

любой компилируется нормально!

person TankorSmash    schedule 12.11.2019