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

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

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

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

Attribute.hpp / CPP

class Attribute {
...

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

friend class cereal::access;
...

CEREAL_REGISTER_TYPE(mgraph::Attribute)

Idable.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)

Position.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)

Account.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 (см. Ниже), который принимает постоянную пустоту *.

(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

2

Решение

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

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

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 передаем его в Base, мы имеем:

(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. У нас есть этот ptr для Base. Здесь мы имеем:

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

Который заканчивается броском в пустоту *. Позже, основываясь на информации о типе, мы приведем этот тип из зарегистрированного полиморфного типа. Так как 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;
}
2

Другие решения

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

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

По вопросам рекламы ammmcru@yandex.ru
Adblock
detector