Boost logo

Boost :

Subject: [boost] serialization crash with polymorphic classes, shared_ptr and std::map
From: Allan Johns (allan.johns_at_[hidden])
Date: 2010-12-08 22:01:57


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


Boost list run by bdawes at acm.org, gregod at cs.rpi.edu, cpdaniel at pacbell.net, john at johnmaddock.co.uk