Зерновая сериализация и полиморфизм
Итак, я столкнулся с проблемой в 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;
}
Я думаю, что у меня была похожая проблема. Некоторая статика не была правильно инициализирована в зерновых. Наконец, я смог найти решение, поместив этот код в исходный файл, который создает экземпляры входных архивов.
#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);
Это не ответ на ваш конкретный вопрос, но он очень похож. Я пытался сериализовать структуру с помощью внешней функции сериализации, но получал ошибку о том, что моя производная структура не может быть преобразована в базовый тип структуры. Уловка заключалась в том, что я не передавал указатель.
Так
ar(cereal::base_class<Base>(derived_instance), derived_instance.y)
стал
ar(cereal::base_class<Base>(&derived_instance), derived_instance.y)
любой компилируется нормально!