谷物序列化和多态性
cereal serialization and polymorphism
好的,所以我 运行 遇到了 C++11 中的谷物问题 (http://uscilab.github.io/cereal/)。
在抽象意义上,我有一个大图,我正在用许多连接边和顶点的共享指针对其进行序列化。边(和顶点)也有附加的属性。
现在这些属性之一 (base class) 是一个帐户 (child class)。 Account 也继承自 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,我将几个堆栈帧上升到这一行:/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()))
= {<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
= std::shared_ptr (count 3, weak 0) 0x758ad0
一切看起来都很好。但是请注意,当我将它转换为 void*:
= std::shared_ptr (count 3, weak 0) 0x758ad0
(gdb) print *((mfin::Account*)((void*)ptr.get()))
= {<mcommon::Idable> = {_vptr.Idable = 0x4f0d78 <vtable for mfin::Account+56>,
id = "3aL[=16=]0[=16=]0[=16=]0[=16=]0[=16=]0PbL[=16=]0[=16=]0[=16=]0[=16=]0[=16=]041L[=16=]0[=16=]0[=16=]0[=16=]0[=16=]01#L", '[=16=]0' <repeats 13 times>, " 2N", '[=16=]0' <repeats 21 times>, "P1@[=16=]0[=16=]0[=16=]0[=16=]0[=16=]007777777 2N", '[=16=]0' <repeats 21 times>, "41L[=16=]0[=16=]0[=16=]0[=16=]0[=16=]0P1@", '[=16=]0' <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> "3aL", 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
好的,经过大量调查,我相信我已经找到了问题的答案。我相信这是库中的错误。在我与图书馆所有者确认后,我将确保这是最新的结果。
我在下面制作了一个简单的程序来演示这个问题。问题源于多重继承、多态性和强制转换。在下面的程序中,我们创建了一个 Derived 对象。 Derived 对象在内存中布局时的格式约为:
Derived:
Base2::vtable
Base2::var
Base::vtable
考虑:
(gdb) print ptr
= std::shared_ptr (count 1, weak 0) 0x63c580
(gdb) print *ptr
= (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
= std::shared_ptr (count 2, weak 0) 0x63c590
(gdb) print *ptr
= (Base &) @0x63c590: {_vptr.Base = 0x421fa8 <vtable for Derived+40>}
这就是问题开始的地方。现在在 /usr/include/cereal/types/polymorphic.hpp,第 341 行。我们有这个指向 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 是一个 Base* 被转换为 void*,然后转换为 Derived*。因此,转换链会导致无效对象。如下所示,ptr 现在无效:
(gdb) print *ptr
= (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>}
指针指向 Base 的 vtable,而不是 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)
任何编译都很好!
好的,所以我 运行 遇到了 C++11 中的谷物问题 (http://uscilab.github.io/cereal/)。
在抽象意义上,我有一个大图,我正在用许多连接边和顶点的共享指针对其进行序列化。边(和顶点)也有附加的属性。
现在这些属性之一 (base class) 是一个帐户 (child class)。 Account 也继承自 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,我将几个堆栈帧上升到这一行:/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()))
= {<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
= std::shared_ptr (count 3, weak 0) 0x758ad0
一切看起来都很好。但是请注意,当我将它转换为 void*:
= std::shared_ptr (count 3, weak 0) 0x758ad0
(gdb) print *((mfin::Account*)((void*)ptr.get()))
= {<mcommon::Idable> = {_vptr.Idable = 0x4f0d78 <vtable for mfin::Account+56>,
id = "3aL[=16=]0[=16=]0[=16=]0[=16=]0[=16=]0PbL[=16=]0[=16=]0[=16=]0[=16=]0[=16=]041L[=16=]0[=16=]0[=16=]0[=16=]0[=16=]01#L", '[=16=]0' <repeats 13 times>, " 2N", '[=16=]0' <repeats 21 times>, "P1@[=16=]0[=16=]0[=16=]0[=16=]0[=16=]007777777 2N", '[=16=]0' <repeats 21 times>, "41L[=16=]0[=16=]0[=16=]0[=16=]0[=16=]0P1@", '[=16=]0' <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> "3aL", 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
好的,经过大量调查,我相信我已经找到了问题的答案。我相信这是库中的错误。在我与图书馆所有者确认后,我将确保这是最新的结果。
我在下面制作了一个简单的程序来演示这个问题。问题源于多重继承、多态性和强制转换。在下面的程序中,我们创建了一个 Derived 对象。 Derived 对象在内存中布局时的格式约为:
Derived:
Base2::vtable
Base2::var
Base::vtable
考虑:
(gdb) print ptr
= std::shared_ptr (count 1, weak 0) 0x63c580
(gdb) print *ptr
= (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
= std::shared_ptr (count 2, weak 0) 0x63c590
(gdb) print *ptr
= (Base &) @0x63c590: {_vptr.Base = 0x421fa8 <vtable for Derived+40>}
这就是问题开始的地方。现在在 /usr/include/cereal/types/polymorphic.hpp,第 341 行。我们有这个指向 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 是一个 Base* 被转换为 void*,然后转换为 Derived*。因此,转换链会导致无效对象。如下所示,ptr 现在无效:
(gdb) print *ptr
= (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>}
指针指向 Base 的 vtable,而不是 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)
任何编译都很好!