lundi 27 avril 2015

cereal serialization and polymorphism

Ok so I'm running into a problem in C++11 with cereal (http://ift.tt/1jDSDLg).

In an abstract sense I have a large graph which I am serializing with lots of shared pointers connecting the edges and vertices. Edges (and vertices) also have attributes attached to them.

Now one of these attributes (base class) is an account (child class). Account also inherits from Idable which also is serializable. Now here are some pertinent snips of code which show some of my cereal usage. I'll explain the issue after this context:

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)

So the problem comes when a mfin::Account is being serialized. The mfin::Account belongs to a std::list>. When we get down into the serialize function for Idable the object is invalid.

Going into gdb which halts on a segfault I go up a few stackframes to this this line: /usr/include/cereal/types/polymorphic.hpp:341. Which is:

(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

Now here this is what ptr is:

(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

Everything is looking good. But notice when I cast it to a void*:

$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)}}

This is of course what happens in binding->second.shared_ptr (seen below) which takes a 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 =

Now I believe this is expected as if I recall correctly there is something wrong about casting a polymorphic type to a void*. What is wrong in my usage of cereal that would cause this?

Aucun commentaire:

Enregistrer un commentaire