serialization crash with polymorphic classes, shared_ptr and std::map

Hi. I have hit a problem with boost serialization (occurs in all versions of boost - 1.37.0 .. 1.45.0). Summary: I have virtual base class A, derived B, and an AMap class (also A-derived) which holds shared_ptr<A>'s. I get different data written out depending on whether I serialise out an AMap stored in a shared_ptr<A> or a shared_ptr<const A>, and in one case (non-const) I get a segfault on load. The following code illustrates the problem in entirety and is the simplest case I found which generates the problem (I tried several variations - saving the map directly, removing A as AMap's base class etc, etc, to no avail). You should be able to cut'n'paste and compile into a single executable. I am testing on linux + centOS. ############################################################################################################## lib.h #ifndef __TESTLIB__H_ #define __TESTLIB__H_ #include <map> #include <boost/serialization/nvp.hpp> #include <boost/serialization/shared_ptr.hpp> #include <boost/serialization/base_object.hpp> class A { public: A(){} virtual ~A(){} template<class Archive> void serialize(Archive & ar, const unsigned int version){} }; typedef boost::shared_ptr<A> a_ptr; typedef boost::shared_ptr<const A> c_a_ptr; typedef std::map<int, a_ptr> a_map; class B : public A{ public: B(){} virtual ~B(){} template<class Archive> void serialize(Archive & ar, const unsigned int version){ ar & boost::serialization::make_nvp("base_class", boost::serialization::base_object<A>(*this)); } }; class AMap : public A { public: AMap(){} virtual ~AMap(){} template<class Archive> void serialize(Archive & ar, const unsigned int version){ ar & boost::serialization::make_nvp("base_class_a", boost::serialization::base_object<A>(*this)); ar & boost::serialization::make_nvp("map", m_map); } a_map m_map; }; #endif ############################################################################################################## lib.cpp #include <boost/archive/xml_oarchive.hpp> #include <boost/archive/xml_iarchive.hpp> #include "lib.h" #include <boost/serialization/map.hpp> #include <boost/serialization/export.hpp> BOOST_CLASS_EXPORT(A); BOOST_CLASS_EXPORT(B); BOOST_CLASS_EXPORT(AMap); ############################################################################################################## main.cpp #include <boost/archive/xml_oarchive.hpp> #include <boost/archive/xml_iarchive.hpp> #include <boost/serialization/map.hpp> #include <fstream> #include <iostream> #include "lib.h" const char* fname="/tmp/testlib.xml"; void save(*a_ptr* a) // NOTE!!! change me to c_a_ptr to get a segfault on load { std::ofstream fs(fname); boost::archive::xml_oarchive ar(fs); ar & boost::serialization::make_nvp("root", a); } int main(int argc, char** argv) { { std::cout << "saving..." << std::endl; AMap* pam = new AMap(); a_ptr a(new B()); pam->m_map.insert(a_map::value_type(1, a)); a_ptr am(pam); save(am); } { std::cout << "loading..." << std::endl; a_ptr am; std::ifstream fs(fname); boost::archive::xml_iarchive ar(fs); ar & boost::serialization::make_nvp("root", am); assert(am); } return 0; } Note that changing main's save() arg to a shared_ptr<const A> changes the data written out. Here is an example xml archive resulting from saving via a non-const shared_ptr (the case that WORKS on load): <?xml version="1.0" encoding="UTF-8" standalone="yes" ?> <!DOCTYPE boost_serialization> <boost_serialization signature="serialization::archive" version="5"> <root class_id="0" tracking_level="0" version="1"> <px class_id="2" class_name="AMap" tracking_level="1" version="0" object_id="_0"> <base_class_a class_id="1" tracking_level="1" version="0" object_id="_1"></base_class_a> <base_class_map class_id="3" tracking_level="0" version="0"> <count>1</count> <item_version>0</item_version> <item class_id="4" tracking_level="0" version="0"> <first>1</first> <second> <px class_id="5" class_name="B" tracking_level="1" version="0" object_id="_2"> <base_class object_id="_3"></base_class> </px> </second> </item> </base_class_map> </px> </root> </boost_serialization> Here is the same data but written out via the CONST shared_ptr (the case that FAILS on load): <?xml version="1.0" encoding="UTF-8" standalone="yes" ?> <!DOCTYPE boost_serialization> <boost_serialization signature="serialization::archive" version="5"> <root class_id="0" tracking_level="0" version="1"> <px class_id="2" class_name="AMap" tracking_level="1" version="0" object_id="_0"> <base_class_a class_id="1" tracking_level="1" version="0" object_id="_1"></base_class_a> <base_class_map class_id="3" tracking_level="0" version="0"> <count>1</count> <item_version>0</item_version> <item class_id="4" tracking_level="0" version="0"> <first>1</first> <second *class_id="5" tracking_level="0" version="1"*> <px class_id="6" class_name="B" tracking_level="1" version="0" object_id="_2"> <base_class object_id="_3"></base_class> </px> </second> </item> </base_class_map> </px> </root> </boost_serialization> Note the difference, which I've put in bold. If somebody could explain what is happening here I'd be very grateful. Obviously something is going on with the object tracking... and what I find most strange is that the data that loads correctly actually looks incorrect (<second> doesn't have tracking-level=0, whereas tracking-level=0 wherever else there's a shared_ptr, as I'd expect). Note: I have tested using the xml and binary archives... xml segfaults, binary throws a stream_error. Thanks in advance Allan

Further to this, I've removed std::map from the equation. The same problem occurs when AMap contains just a shared_ptr<A>. Code is as follows: ############################################################################################################## lib.h #ifndef __TESTLIB__H_ #define __TESTLIB__H_ #include <boost/serialization/nvp.hpp> #include <boost/serialization/shared_ptr.hpp> #include <boost/serialization/base_object.hpp> class A { public: A(){} virtual ~A(){} template<class Archive> void serialize(Archive & ar, const unsigned int version){} }; typedef boost::shared_ptr<A> a_ptr; typedef boost::shared_ptr<const A> c_a_ptr; class B : public A{ public: B(){} virtual ~B(){} template<class Archive> void serialize(Archive & ar, const unsigned int version){ ar & boost::serialization::make_nvp("base_class", boost::serialization::base_object<A>(*this)); } }; class AMap : public A { public: AMap(){} virtual ~AMap(){} template<class Archive> void serialize(Archive & ar, const unsigned int version){ ar & boost::serialization::make_nvp("base_class_a", boost::serialization::base_object<A>(*this)); ar & boost::serialization::make_nvp("a", m_a); } a_ptr m_a; }; #endif ############################################################################################################## lib.cpp #include <boost/archive/xml_oarchive.hpp> #include <boost/archive/xml_iarchive.hpp> #include "lib.h" #include <boost/serialization/export.hpp> BOOST_CLASS_EXPORT(A); BOOST_CLASS_EXPORT(B); BOOST_CLASS_EXPORT(AMap); ############################################################################################################## main.cpp #include <boost/archive/xml_oarchive.hpp> #include <boost/archive/xml_iarchive.hpp> #include <fstream> #include <iostream> #include "lib.h" const char* fname="/tmp/testlib.xml"; void save(c_a_ptr a) { std::ofstream fs(fname); boost::archive::xml_oarchive ar(fs); ar & boost::serialization::make_nvp("root", a); } int main(int argc, char** argv) { { std::cout << "saving..." << std::endl; AMap* pam = new AMap(); pam->m_a = a_ptr(new B()); a_ptr am(pam); save(am); } { std::cout << "loading..." << std::endl; a_ptr am; std::ifstream fs(fname); boost::archive::xml_iarchive ar(fs); ar & boost::serialization::make_nvp("root", am); assert(am); } return 0; } On Thu, Dec 9, 2010 at 2:01 PM, Allan Johns <allan.johns@drdstudios.com>wrote:
Hi. I have hit a problem with boost serialization (occurs in all versions of boost - 1.37.0 .. 1.45.0).
Summary: I have virtual base class A, derived B, and an AMap class (also A-derived) which holds shared_ptr<A>'s. I get different data written out depending on whether I serialise out an AMap stored in a shared_ptr<A> or a shared_ptr<const A>, and in one case (non-const) I get a segfault on load.
The following code illustrates the problem in entirety and is the simplest case I found which generates the problem (I tried several variations - saving the map directly, removing A as AMap's base class etc, etc, to no avail). You should be able to cut'n'paste and compile into a single executable. I am testing on linux + centOS.
############################################################################################################## lib.h
#ifndef __TESTLIB__H_ #define __TESTLIB__H_
#include <map> #include <boost/serialization/nvp.hpp> #include <boost/serialization/shared_ptr.hpp> #include <boost/serialization/base_object.hpp>
class A { public: A(){} virtual ~A(){} template<class Archive> void serialize(Archive & ar, const unsigned int version){} };
typedef boost::shared_ptr<A> a_ptr; typedef boost::shared_ptr<const A> c_a_ptr; typedef std::map<int, a_ptr> a_map;
class B : public A{ public: B(){} virtual ~B(){} template<class Archive> void serialize(Archive & ar, const unsigned int version){ ar & boost::serialization::make_nvp("base_class", boost::serialization::base_object<A>(*this)); } };
class AMap : public A { public: AMap(){} virtual ~AMap(){} template<class Archive> void serialize(Archive & ar, const unsigned int version){ ar & boost::serialization::make_nvp("base_class_a", boost::serialization::base_object<A>(*this)); ar & boost::serialization::make_nvp("map", m_map); }
a_map m_map; };
#endif
############################################################################################################## lib.cpp
#include <boost/archive/xml_oarchive.hpp> #include <boost/archive/xml_iarchive.hpp> #include "lib.h"
#include <boost/serialization/map.hpp> #include <boost/serialization/export.hpp>
BOOST_CLASS_EXPORT(A); BOOST_CLASS_EXPORT(B); BOOST_CLASS_EXPORT(AMap);
############################################################################################################## main.cpp
#include <boost/archive/xml_oarchive.hpp> #include <boost/archive/xml_iarchive.hpp> #include <boost/serialization/map.hpp> #include <fstream> #include <iostream>
#include "lib.h"
const char* fname="/tmp/testlib.xml";
void save(*a_ptr* a) // NOTE!!! change me to c_a_ptr to get a segfault on load { std::ofstream fs(fname); boost::archive::xml_oarchive ar(fs); ar & boost::serialization::make_nvp("root", a); }
int main(int argc, char** argv) { { std::cout << "saving..." << std::endl; AMap* pam = new AMap(); a_ptr a(new B()); pam->m_map.insert(a_map::value_type(1, a));
a_ptr am(pam); save(am); }
{ std::cout << "loading..." << std::endl; a_ptr am; std::ifstream fs(fname); boost::archive::xml_iarchive ar(fs); ar & boost::serialization::make_nvp("root", am); assert(am); }
return 0; }
Note that changing main's save() arg to a shared_ptr<const A> changes the data written out.
Here is an example xml archive resulting from saving via a non-const shared_ptr (the case that WORKS on load):
<?xml version="1.0" encoding="UTF-8" standalone="yes" ?> <!DOCTYPE boost_serialization> <boost_serialization signature="serialization::archive" version="5"> <root class_id="0" tracking_level="0" version="1"> <px class_id="2" class_name="AMap" tracking_level="1" version="0" object_id="_0"> <base_class_a class_id="1" tracking_level="1" version="0" object_id="_1"></base_class_a> <base_class_map class_id="3" tracking_level="0" version="0"> <count>1</count> <item_version>0</item_version> <item class_id="4" tracking_level="0" version="0"> <first>1</first> <second> <px class_id="5" class_name="B" tracking_level="1" version="0" object_id="_2"> <base_class object_id="_3"></base_class> </px> </second> </item> </base_class_map> </px> </root> </boost_serialization>
Here is the same data but written out via the CONST shared_ptr (the case that FAILS on load):
<?xml version="1.0" encoding="UTF-8" standalone="yes" ?> <!DOCTYPE boost_serialization> <boost_serialization signature="serialization::archive" version="5"> <root class_id="0" tracking_level="0" version="1"> <px class_id="2" class_name="AMap" tracking_level="1" version="0" object_id="_0"> <base_class_a class_id="1" tracking_level="1" version="0" object_id="_1"></base_class_a> <base_class_map class_id="3" tracking_level="0" version="0"> <count>1</count> <item_version>0</item_version> <item class_id="4" tracking_level="0" version="0"> <first>1</first> <second *class_id="5" tracking_level="0" version="1"*> <px class_id="6" class_name="B" tracking_level="1" version="0" object_id="_2"> <base_class object_id="_3"></base_class> </px> </second> </item> </base_class_map> </px> </root> </boost_serialization>
Note the difference, which I've put in bold.
If somebody could explain what is happening here I'd be very grateful. Obviously something is going on with the object tracking... and what I find most strange is that the data that loads correctly actually looks incorrect (<second> doesn't have tracking-level=0, whereas tracking-level=0 wherever else there's a shared_ptr, as I'd expect).
Note: I have tested using the xml and binary archives... xml segfaults, binary throws a stream_error.
Thanks in advance Allan

Try making the changes below Robert Ramey Allan Johns wrote:
Hi. I have hit a problem with boost serialization (occurs in all versions of boost - 1.37.0 .. 1.45.0).
Summary: I have virtual base class A, derived B, and an AMap class (also A-derived) which holds shared_ptr<A>'s. I get different data written out depending on whether I serialise out an AMap stored in a shared_ptr<A> or a shared_ptr<const A>, and in one case (non-const) I get a segfault on load.
The following code illustrates the problem in entirety and is the simplest case I found which generates the problem (I tried several variations - saving the map directly, removing A as AMap's base class etc, etc, to no avail). You should be able to cut'n'paste and compile into a single executable. I am testing on linux + centOS.
############################################################################################################## lib.h
#ifndef __TESTLIB__H_ #define __TESTLIB__H_
#include <map> #include <boost/serialization/nvp.hpp> #include <boost/serialization/shared_ptr.hpp> #include <boost/serialization/base_object.hpp>
class A { public: A(){} virtual ~A(){} template<class Archive> void serialize(Archive & ar, const unsigned int version){} };
typedef boost::shared_ptr<A> a_ptr; typedef boost::shared_ptr<const A> c_a_ptr; typedef std::map<int, a_ptr> a_map;
class B : public A{ public: B(){} virtual ~B(){} template<class Archive> void serialize(Archive & ar, const unsigned int version){ ar & boost::serialization::make_nvp("base_class", boost::serialization::base_object<A>(*this)); } };
class AMap : public A { public: AMap(){} virtual ~AMap(){} template<class Archive> void serialize(Archive & ar, const unsigned int version){ ar & boost::serialization::make_nvp("base_class_a", boost::serialization::base_object<A>(*this)); ar & boost::serialization::make_nvp("map", m_map); }
a_map m_map; };
#endif
############################################################################################################## lib.cpp
#include <boost/archive/xml_oarchive.hpp> #include <boost/archive/xml_iarchive.hpp> #include "lib.h"
#include <boost/serialization/map.hpp> #include <boost/serialization/export.hpp>
// drop the folling:
BOOST_CLASS_EXPORT(A); BOOST_CLASS_EXPORT(B); BOOST_CLASS_EXPORT(AMap);
// insert the following BOOST_SERIALIZATION_SHARED_PTR(B)
############################################################################################################## main.cpp
#include <boost/archive/xml_oarchive.hpp> #include <boost/archive/xml_iarchive.hpp> #include <boost/serialization/map.hpp> #include <fstream> #include <iostream>
#include "lib.h"
const char* fname="/tmp/testlib.xml";
void save(*a_ptr* a) // NOTE!!! change me to c_a_ptr to get a segfault on load { std::ofstream fs(fname); boost::archive::xml_oarchive ar(fs); ar & boost::serialization::make_nvp("root", a); }
int main(int argc, char** argv) { { std::cout << "saving..." << std::endl; AMap* pam = new AMap(); a_ptr a(new B()); pam->m_map.insert(a_map::value_type(1, a));
a_ptr am(pam); save(am); }
{ std::cout << "loading..." << std::endl; a_ptr am; std::ifstream fs(fname); boost::archive::xml_iarchive ar(fs); ar & boost::serialization::make_nvp("root", am); assert(am); }
return 0; }
Note that changing main's save() arg to a shared_ptr<const A> changes the data written out.
Here is an example xml archive resulting from saving via a non-const shared_ptr (the case that WORKS on load):
<?xml version="1.0" encoding="UTF-8" standalone="yes" ?> <!DOCTYPE boost_serialization> <boost_serialization signature="serialization::archive" version="5"> <root class_id="0" tracking_level="0" version="1"> <px class_id="2" class_name="AMap" tracking_level="1" version="0" object_id="_0"> <base_class_a class_id="1" tracking_level="1" version="0" object_id="_1"></base_class_a> <base_class_map class_id="3" tracking_level="0" version="0"> <count>1</count> <item_version>0</item_version> <item class_id="4" tracking_level="0" version="0"> <first>1</first> <second> <px class_id="5" class_name="B" tracking_level="1" version="0" object_id="_2"> <base_class object_id="_3"></base_class> </px> </second> </item> </base_class_map> </px> </root> </boost_serialization>
Here is the same data but written out via the CONST shared_ptr (the case that FAILS on load):
<?xml version="1.0" encoding="UTF-8" standalone="yes" ?> <!DOCTYPE boost_serialization> <boost_serialization signature="serialization::archive" version="5"> <root class_id="0" tracking_level="0" version="1"> <px class_id="2" class_name="AMap" tracking_level="1" version="0" object_id="_0"> <base_class_a class_id="1" tracking_level="1" version="0" object_id="_1"></base_class_a> <base_class_map class_id="3" tracking_level="0" version="0"> <count>1</count> <item_version>0</item_version> <item class_id="4" tracking_level="0" version="0"> <first>1</first> <second *class_id="5" tracking_level="0" version="1"*> <px class_id="6" class_name="B" tracking_level="1" version="0" object_id="_2"> <base_class object_id="_3"></base_class> </px> </second> </item> </base_class_map> </px> </root> </boost_serialization>
Note the difference, which I've put in bold.
If somebody could explain what is happening here I'd be very grateful. Obviously something is going on with the object tracking... and what I find most strange is that the data that loads correctly actually looks incorrect (<second> doesn't have tracking-level=0, whereas tracking-level=0 wherever else there's a shared_ptr, as I'd expect).
Note: I have tested using the xml and binary archives... xml segfaults, binary throws a stream_error.
Thanks in advance Allan _______________________________________________ Unsubscribe & other changes: http://lists.boost.org/mailman/listinfo.cgi/boost

Hi Robert, Thanks for the help, however I've discovered and fixed the problem (but some more light on the matter would still be great). It seems that serialization of shared_ptr<T> can differ to that of shared_ptr<const T> - the object tracking changes in some cases. I suppose this makes sense, given that the two are indeed different types. thanks again A On Fri, Dec 10, 2010 at 6:41 AM, Robert Ramey <ramey@rrsd.com> wrote:
Try making the changes below
Robert Ramey
Allan Johns wrote:
Hi. I have hit a problem with boost serialization (occurs in all versions of boost - 1.37.0 .. 1.45.0).
Summary: I have virtual base class A, derived B, and an AMap class (also A-derived) which holds shared_ptr<A>'s. I get different data written out depending on whether I serialise out an AMap stored in a shared_ptr<A> or a shared_ptr<const A>, and in one case (non-const) I get a segfault on load.
The following code illustrates the problem in entirety and is the simplest case I found which generates the problem (I tried several variations - saving the map directly, removing A as AMap's base class etc, etc, to no avail). You should be able to cut'n'paste and compile into a single executable. I am testing on linux + centOS.
##############################################################################################################
lib.h
#ifndef __TESTLIB__H_ #define __TESTLIB__H_
#include <map> #include <boost/serialization/nvp.hpp> #include <boost/serialization/shared_ptr.hpp> #include <boost/serialization/base_object.hpp>
class A { public: A(){} virtual ~A(){} template<class Archive> void serialize(Archive & ar, const unsigned int version){} };
typedef boost::shared_ptr<A> a_ptr; typedef boost::shared_ptr<const A> c_a_ptr; typedef std::map<int, a_ptr> a_map;
class B : public A{ public: B(){} virtual ~B(){} template<class Archive> void serialize(Archive & ar, const unsigned int version){ ar & boost::serialization::make_nvp("base_class", boost::serialization::base_object<A>(*this)); } };
class AMap : public A { public: AMap(){} virtual ~AMap(){} template<class Archive> void serialize(Archive & ar, const unsigned int version){ ar & boost::serialization::make_nvp("base_class_a", boost::serialization::base_object<A>(*this)); ar & boost::serialization::make_nvp("map", m_map); }
a_map m_map; };
#endif
##############################################################################################################
lib.cpp
#include <boost/archive/xml_oarchive.hpp> #include <boost/archive/xml_iarchive.hpp> #include "lib.h"
#include <boost/serialization/map.hpp> #include <boost/serialization/export.hpp>
// drop the folling:
BOOST_CLASS_EXPORT(A); BOOST_CLASS_EXPORT(B); BOOST_CLASS_EXPORT(AMap);
// insert the following BOOST_SERIALIZATION_SHARED_PTR(B)
##############################################################################################################
main.cpp
#include <boost/archive/xml_oarchive.hpp> #include <boost/archive/xml_iarchive.hpp> #include <boost/serialization/map.hpp> #include <fstream> #include <iostream>
#include "lib.h"
const char* fname="/tmp/testlib.xml";
void save(*a_ptr* a) // NOTE!!! change me to c_a_ptr to get a segfault on load { std::ofstream fs(fname); boost::archive::xml_oarchive ar(fs); ar & boost::serialization::make_nvp("root", a); }
int main(int argc, char** argv) { { std::cout << "saving..." << std::endl; AMap* pam = new AMap(); a_ptr a(new B()); pam->m_map.insert(a_map::value_type(1, a));
a_ptr am(pam); save(am); }
{ std::cout << "loading..." << std::endl; a_ptr am; std::ifstream fs(fname); boost::archive::xml_iarchive ar(fs); ar & boost::serialization::make_nvp("root", am); assert(am); }
return 0; }
Note that changing main's save() arg to a shared_ptr<const A> changes the data written out.
Here is an example xml archive resulting from saving via a non-const shared_ptr (the case that WORKS on load):
<?xml version="1.0" encoding="UTF-8" standalone="yes" ?> <!DOCTYPE boost_serialization> <boost_serialization signature="serialization::archive" version="5"> <root class_id="0" tracking_level="0" version="1"> <px class_id="2" class_name="AMap" tracking_level="1" version="0" object_id="_0"> <base_class_a class_id="1" tracking_level="1" version="0" object_id="_1"></base_class_a> <base_class_map class_id="3" tracking_level="0" version="0"> <count>1</count> <item_version>0</item_version> <item class_id="4" tracking_level="0" version="0"> <first>1</first> <second> <px class_id="5" class_name="B" tracking_level="1" version="0" object_id="_2"> <base_class object_id="_3"></base_class> </px> </second> </item> </base_class_map> </px> </root> </boost_serialization>
Here is the same data but written out via the CONST shared_ptr (the case that FAILS on load):
<?xml version="1.0" encoding="UTF-8" standalone="yes" ?> <!DOCTYPE boost_serialization> <boost_serialization signature="serialization::archive" version="5"> <root class_id="0" tracking_level="0" version="1"> <px class_id="2" class_name="AMap" tracking_level="1" version="0" object_id="_0"> <base_class_a class_id="1" tracking_level="1" version="0" object_id="_1"></base_class_a> <base_class_map class_id="3" tracking_level="0" version="0"> <count>1</count> <item_version>0</item_version> <item class_id="4" tracking_level="0" version="0"> <first>1</first> <second *class_id="5" tracking_level="0" version="1"*> <px class_id="6" class_name="B" tracking_level="1" version="0" object_id="_2"> <base_class object_id="_3"></base_class> </px> </second> </item> </base_class_map> </px> </root> </boost_serialization>
Note the difference, which I've put in bold.
If somebody could explain what is happening here I'd be very grateful. Obviously something is going on with the object tracking... and what I find most strange is that the data that loads correctly actually looks incorrect (<second> doesn't have tracking-level=0, whereas tracking-level=0 wherever else there's a shared_ptr, as I'd expect).
Note: I have tested using the xml and binary archives... xml segfaults, binary throws a stream_error.
Thanks in advance Allan _______________________________________________ Unsubscribe & other changes: http://lists.boost.org/mailman/listinfo.cgi/boost
_______________________________________________ Unsubscribe & other changes: http://lists.boost.org/mailman/listinfo.cgi/boost
participants (2)
-
Allan Johns
-
Robert Ramey