2015-04-28 2 views
2

Хорошо, поэтому я столкнулся с проблемой в 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\[email protected]\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\[email protected]", '\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 
+0

FYI, * "cereal" * - это еда, основанная на зерне, обычно съедается на завтрак. «Serial» - это процесс передачи одной вещи за раз, например, бит на жесткий диск. –

+2

это также название библиотеки, http://uscilab.github.io/cereal/, спасибо, хотя –

+0

Спасибо! . Теперь я жажду петли Froot! –

ответ

2

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

Я разработал простую программу, ниже которой демонстрируется эта проблема. Проблема связана с множественным наследованием, полиморфизмом и литьем. В программе ниже было указано, что мы создаем объект Derived. Выведенный в память объект 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. У нас есть этот ptr для Base. Здесь у нас есть:

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, которая является базовой * был приведен к аннулированию *, а затем приведение к 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>} 

Указатель указывает на таблицу виртуальных для базы, а не производный/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; 
} 
0

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

#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

Это была ошибка в библиотеке, которая была исправлена: https://github.com/USCiLab/cereal/issues/188 –

+0

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

+1

да, но я хочу сказать, что это не то же самое, что вопрос –

Смежные вопросы