Search code examples
c++serializationboostdeserializationshared-ptr

Introduction of shared_ptr leads to a segmentation fault upon deserialization (with boost::serialization)


I have a class gamma, as shown below.

The load function throws a segmentation fault, when the make_nvp function tries to deserialize an existing xml file. The error comes when I use std::shared_ptr<std::tuple<double,double,double>> val;

If instead val is just std::tuple<double,double,double> val;

then everything seems to work fine (Of course, I change the getter and setter functions accordingly).

Now I went over a bunch of questions on stackoverflow and I googled and looked at the examples in boost documentation and I can't figure out, why the load function causes the program to terminate with a segmentation fault.

Note: Some old posts on the internet (and some older questions on stackoverflow) seem to imply that std::shared_ptr used to not work with boost serialization at that point in time. I don't think this is the case in 2017. Anyways, just to be sure, I tried replacing the std::shared_ptr with a boost::shared_ptr and the segmentation error was still there.

I don't understand/see why the error appears?

gamma.h

#pragma once
#include <map>
#include <boost/serialization/access.hpp>
#include <boost/serialization/nvp.hpp>
#include <boost/serialization/map.hpp>
#include <boost/serialization/vector.hpp>
#include <boost/serialization/utility.hpp>
#include <boost/serialization/shared_ptr.hpp>

#include <tuple>

namespace boost
{
    namespace serialization
    {
        template<typename Archive>
        void serialize(Archive & ar, std::tuple<double, double, double> & t,
                    const unsigned int version)
        {
            ar & boost::serialization::make_nvp("t0",std::get<0>(t));
            ar & boost::serialization::make_nvp("t1",std::get<1>(t));
            ar & boost::serialization::make_nvp("t2",std::get<2>(t));
        }

    }
}

class Gamma
{
public:
    static void save(std::ostream& os);
    static void load(std::istream& is);

    std::shared_ptr<std::tuple<double, double, double>> getterX() const;
    void setterX(const std::tuple<double, double, double> &val);

private:

    std::shared_ptr<std::tuple<double,double,double>> val;

    friend class boost::serialization::access;
    template<typename Archive>
    void serialize(Archive& arc, const unsigned int version)
    {
          arc & boost::serialization::make_nvp("val", val);
    }
};

and gamma.cpp

#include "gamma.h"
#include <boost/archive/xml_iarchive.hpp>
#include <boost/archive/xml_oarchive.hpp>
#include <boost/serialization/access.hpp>
#include <boost/serialization/nvp.hpp>
#include <boost/serialization/utility.hpp>

Gamma &Gamma::instance()
{
    static Gamma obj;
    return obj;
}

std::shared_ptr<std::tuple<double, double, double>> Gamma::getterX() const
{
    return val;
}

void Gamma::setterX(const std::tuple<double, double,double> &v)
{
    if (nullptr == val) {
        m_touchDownCalibration = std::make_shared<std::tuple<double, double,double>>();
    }
    *val = v;
}

const char* TAG = "tag";

void Gamma::save(std::ostream& os)
{
    boost::archive::xml_oarchive arc(os);
    arc & boost::serialization::make_nvp(TAG,instance());
}

void Gamma::load(std::istream& is)
{
    boost::archive::xml_iarchive arc(is);
    arc & boost::serialization::make_nvp(TAG,instance());
}

Solution

  • It's a mystery to me what a shared_ptr could actually add to a single tuple inside... a singleton. But anyways, I made your code self-contained and it works:

    Live On Coliru

    I hope you can figure out which part you did differently/wrong:

    #include <map>
    #include <boost/serialization/access.hpp>
    #include <boost/serialization/nvp.hpp>
    #include <boost/serialization/map.hpp>
    #include <boost/serialization/vector.hpp>
    #include <boost/serialization/utility.hpp>
    #include <boost/serialization/shared_ptr.hpp>
    
    #include <tuple>
    
    namespace boost { namespace serialization {
        template<typename Archive>
        void serialize(Archive & ar, std::tuple<double, double, double> & t, unsigned) {
            ar & boost::serialization::make_nvp("t0", std::get<0>(t));
            ar & boost::serialization::make_nvp("t1", std::get<1>(t));
            ar & boost::serialization::make_nvp("t2", std::get<2>(t));
        }
    } }
    
    class CalibrationDataObject
    {
    public:
        static CalibrationDataObject &instance();
        static void save(std::ostream& os);
        static void load(std::istream& is);
    
        std::shared_ptr<std::tuple<double, double, double>> getterX() const;
        void setterX(const std::tuple<double, double, double> &val);
    
    private:
        std::shared_ptr<std::tuple<double,double,double>> val;
    
        friend class boost::serialization::access;
        template<typename Archive>
        void serialize(Archive& arc, unsigned)
        {
              arc & boost::serialization::make_nvp("val", val);
        }
    };
    
    //#include "CalibrationDataObject.h"
    #include <boost/archive/xml_iarchive.hpp>
    #include <boost/archive/xml_oarchive.hpp>
    #include <boost/serialization/access.hpp>
    #include <boost/serialization/nvp.hpp>
    #include <boost/serialization/utility.hpp>
    
    CalibrationDataObject &CalibrationDataObject::instance() { static CalibrationDataObject obj; return obj; }
    
    std::shared_ptr<std::tuple<double, double, double>> CalibrationDataObject::getterX() const {
        return val;
    }
    
    void CalibrationDataObject::setterX(const std::tuple<double, double,double> &v)
    {
        if (val)
            *val = v;
        else 
            val = std::make_shared<std::tuple<double, double,double>>(v);
    }
    
    const char* TAG = "tag";
    
    void CalibrationDataObject::save(std::ostream& os)
    {
        boost::archive::xml_oarchive arc(os);
        arc & boost::serialization::make_nvp(TAG,instance());
    }
    
    void CalibrationDataObject::load(std::istream& is)
    {
        boost::archive::xml_iarchive arc(is);
        arc & boost::serialization::make_nvp(TAG,instance());
    }
    
    #include <fstream>
    
    int main() {
        {
            std::ofstream ofs("test.data");
            CalibrationDataObject::save(ofs);
        }
        {
            std::ifstream ifs("test.data");
            CalibrationDataObject::load(ifs);
        }
    }
    

    Printing the following data:

    <?xml version="1.0" encoding="UTF-8" standalone="yes" ?>
    <!DOCTYPE boost_serialization>
    <boost_serialization signature="serialization::archive" version="14">
    <tag class_id="0" tracking_level="0" version="0">
        <val class_id="1" tracking_level="0" version="1">
            <px class_id="-1"></px>
        </val>
    </tag>
    </boost_serialization>