2

I want to serialize the following class wrapping a pointer which can handle a null m_element as you can see when calling the default constructor. This follows this question.

Live MCVE on Coliru

template <typename T>
struct Ptr { // Ptr could use init constructor here but this is not the point
    Ptr() { m_elem = 0; }
    Ptr(const T* elem) {
        if (elem)
            m_elem = new T(*elem);
        else
            m_elem = 0;
    }
    Ptr(const T& elem)
    {
        m_elem = new T(elem);
    }
    Ptr(const Ptr& elem)
    {
        if (elem.m_elem)
            m_elem = new T(*(elem.m_elem));
        else
            m_elem = 0;
    }
    virtual ~Ptr() { delete m_elem; m_elem = 0; };

    const T& operator*() const { return *m_elem; };
    T& operator*() { return *m_elem; };

    const T* operator->() const { return m_elem; };
    T* operator->() { return m_elem; };

    T* m_elem;
};

namespace boost { namespace serialization {

    // Not sure about the approach to manage null m_elem here
    template<class Archive, class T>
    void save(Archive & ar, const Ptr<T> &ptr, const unsigned int version)
    {
        T elem = 0;
        if (ptr.m_elem != 0)
            ar& boost::serialization::make_nvp("data", *ptr.m_elem);
        else
            ar& boost::serialization::make_nvp("data", elem);
    }

    // How to implement load ?
    template<class Archive, class T>
    void load(Archive & ar, Ptr<T> &ptr, const unsigned int version)
    {
        ar& boost::serialization::make_nvp("data", *ptr.m_elem);
    }

    template<class Archive, class T>
    void serialize(Archive & ar, Ptr<T> &ptr, const unsigned int version)
    {
        boost::serialization::split_free(ar, ptr, version);
    }

}} // end namespace


int main()
{
    {
        Ptr<A> p;
        std::ostringstream oss;
        boost::archive::xml_oarchive oa(oss);
        oa << BOOST_SERIALIZATION_NVP(p);
        std::cout << oss.str() << std::endl;

        // segfault

        Ptr<double> po;
        std::istringstream iss;
        iss.str(oss.str());
        boost::archive::xml_iarchive ia(iss);
        ia >> BOOST_SERIALIZATION_NVP(po);

    }
    {
        Ptr<double> p(new double(2.0));
        std::cout << *(p.m_elem) << std::endl;

        std::ostringstream oss;
        boost::archive::xml_oarchive oa(oss);
        oa << BOOST_SERIALIZATION_NVP(p);
        std::cout << oss.str() << std::endl;

        // segfault

        Ptr<double> po;
        std::istringstream iss;
        iss.str(oss.str());
        boost::archive::xml_iarchive ia(iss);
        ia >> BOOST_SERIALIZATION_NVP(po);

    }
}

The serialization seems to work but the deserialization gives a segfault. I am working in C++0x.

  • How can I provide safe save and load functions to serialize Ptr without changing Ptr if possible ?
  • If I need to modify Ptr, what do you propose ?

Edit : thanks to Jarod42 comment I came up with the following save/load functions using a boolean to detect null pointer or not. Now I do not have a segfault anymore when m_elem is null but I have a one when it is not null.

template<class Archive, class T>
void save(Archive & ar, const Ptr<T> &ptr, const unsigned int version)
{
    bool is_null;
    if (ptr.m_elem != 0) {
        is_null = false;
        ar& boost::serialization::make_nvp("is_null", is_null);
        ar& boost::serialization::make_nvp("data", *ptr.m_elem);
    }
    else
    {
        is_null = true;
        ar& boost::serialization::make_nvp("is_null", is_null);
    }
}

template<class Archive, class T>
void load(Archive & ar, Ptr<T> &ptr, const unsigned int version)
{
    bool is_null;
    ar& boost::serialization::make_nvp("is_null", is_null);
    if (is_null == true) {
        ptr.m_elem = 0;   
    }
    else
    {
        ar& boost::serialization::make_nvp("data", *ptr.m_elem);
    }
}
Community
  • 1
  • 1
coincoin
  • 4,595
  • 3
  • 23
  • 47
  • You should serialize a boolean to know if pointer is nullptr or not (and if not, serialize also its content). for loading, retrieve this boolean and allocate a default element when needed that you load. – Jarod42 Aug 04 '15 at 12:59
  • Thank you Jarod42 I will try to implement this solution – coincoin Aug 04 '15 at 13:50
  • Now the deserialization works for a null pointer but I have a segfault with the `new double` test case. [Live on Coliru](http://coliru.stacked-crooked.com/a/ff139466f3fa8379) – coincoin Aug 04 '15 at 14:11
  • Erm. What makes this question different from http://stackoverflow.com/questions/31180311/boost-serialization-of-class-wrapping-a-pointer? – sehe Aug 04 '15 at 21:46
  • I could not make the deserialization work properly hence this new question. – coincoin Aug 04 '15 at 23:14

2 Answers2

3

boost::archive's save and load methods understand the difference between pointers and object references. You don't need to specify *m_elem. m_elem will do (and work correctly). Boost will understand if the pointer is null and will simply store a value indicating a null pointer, which will be deserialised correctly.

(simplified) example:

#include <vector>
#include <string>
#include <iostream>
#include <sstream>
#include <algorithm>
#include <iterator>

#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/split_free.hpp>

struct A {
    A() : a(0) {}
    A(int aa) : a(aa) {}
    int a;
    template <class Archive>
    void serialize(Archive& ar, const unsigned int /*version*/)
    {
        ar& BOOST_SERIALIZATION_NVP(a);
    }
};

std::ostream& operator<<(std::ostream& os, const A& a) {
    os << "A{" << a.a << "}";
    return os;
}

template <typename T>
struct Ptr { // Ptr could use init constructor here but this is not the point
    Ptr()
    : m_elem(0)
    {}

    Ptr(T elem)
    : m_elem(new T(elem))
    {
    }

private:
    // no copies
    Ptr(const Ptr&);
    Ptr& operator=(const Ptr&);
public:
    // delete is a NOP when called with nullptr arg
    virtual ~Ptr() { delete m_elem; };

    T* get() const {
        return m_elem;
    }

    T& operator*() const {
        return *m_elem;
    }

    template <class Archive>
    void serialize(Archive& ar, const unsigned int /*version*/)
    {
        ar& BOOST_SERIALIZATION_NVP(m_elem);
    }

private:
    T* m_elem;
};

template<class T>
std::ostream& operator<<(std::ostream& os, const Ptr<T>& p) {
    if (p.get()) {
        os << *p;
    }
    else {
        os << "{nullptr}";
    }
    return os;
}

int main()
{
    std::string payload;

    {
        Ptr<A> p;
        std::cout << p << std::endl;
        std::ostringstream oss;
        boost::archive::xml_oarchive oa(oss);
        oa << BOOST_SERIALIZATION_NVP(p);
        payload = oss.str();
//        std::cout << payload << std::endl;

        Ptr<A> p2(A(6));
        std::cout << p2 << std::endl;
        oa << BOOST_SERIALIZATION_NVP(p2);
        payload = oss.str();
//        std::cout << payload << std::endl;
    }
    {

        Ptr<A> po;
        std::istringstream iss(payload);
        boost::archive::xml_iarchive ia(iss);
        ia >> BOOST_SERIALIZATION_NVP(po);
        std::cout << po << std::endl;

        Ptr<A> po2;
        ia >> BOOST_SERIALIZATION_NVP(po2);
        std::cout << po2 << std::endl;
    }
}

expected output:

{nullptr}
A{6}
{nullptr}
A{6}
Richard Hodges
  • 68,278
  • 7
  • 90
  • 142
  • I have maybe misunderstood but this [live code](http://coliru.stacked-crooked.com/a/5687f9f3509a5f6a) does not compile with `ar& boost::serialization::make_nvp("data", ptr.m_elem);` in change/load functions. I guess it will be the same behaviour in serialize() without splitting. – coincoin Aug 04 '15 at 13:30
  • working example posted above. Note no special treatment of pointers members. I have omitted the splitting behaviour. Suggest you start here and then add in the split save/load. – Richard Hodges Aug 04 '15 at 15:10
  • Thank you for your updates. Unfortunately my second deserialization test with `new double` does not seem to work see [live code here](http://coliru.stacked-crooked.com/a/7108bca7a094fa14). Is it possible to stick with my Ptr version ? I have some copy constructors and also, I am using C++0x so no std::move, forward reference, etc unfortunately. Furthermore, a non intruisive boost serialization is needed here (but I don't think it is revelant here). – coincoin Aug 04 '15 at 15:49
  • sure. actually the use of c++11 is irerrelevant also. I'll modify the example. – Richard Hodges Aug 04 '15 at 15:51
  • @coincoin example updated to use only c++03 code. Note the null pointer being correctly serialised and deserialised. – Richard Hodges Aug 04 '15 at 16:19
  • Thank you again but unfortunately it is not working when calling `Ptr p(new double(2.0));` compile error see [live code](http://coliru.stacked-crooked.com/a/a903a0df5bd7b6b2). I managed to make it work with Jarod42 comment. – coincoin Aug 04 '15 at 16:46
  • well, it looks like you've found a bug in boost::serialization! – Richard Hodges Aug 04 '15 at 17:50
  • I mean how do you explain that the live code I show does not work ? – coincoin Aug 04 '15 at 23:15
-1

Thanks to Jarod42 comment,

You should serialize a boolean to know if pointer is nullptr or not (and if not, serialize also its content). for loading, retrieve this boolean and allocate a default element when needed that you load.

we came with the following save and load functions :

template<class Archive, class T>
void save(Archive & ar, const Ptr<T> &ptr, const unsigned int version)
{
    bool is_null = !ptr.m_elem;
    ar & boost::serialization::make_nvp("is_null", is_null);
    if(!is_null) ar & boost::serialization::make_nvp("data", *ptr.m_elem);
}

template<class Archive, class T>
void load(Archive & ar, Ptr<T> &ptr, const unsigned int version)
{
    bool is_null;
    ar & boost::serialization::make_nvp("is_null", is_null);

    if (!is_null) {
        ptr.m_elem = new T;
        ar& boost::serialization::make_nvp("data", *ptr.m_elem);
    }
}

Live on Coliru

Had an issue in the load function when is_null is false. A storage is indeed needed for ptr in this case.

coincoin
  • 4,595
  • 3
  • 23
  • 47
  • Frankly, this looks like a bad idea. I think I already told you _"You need to rethink your serialization plan. What object identity do you wish/need to track? You can track the identity of the `Ptr<>` object, and from the fact that you took the trouble to implement a custom pointer wrapper type, I get the impression that this is likely all you want/need."_ [a month ago](http://stackoverflow.com/a/31180732/85371) and from a cursory inspection the question appears to be the same? I'd _rethink your serialization plan_. Think about what object tracking _means_ for your "special pointer" – sehe Aug 04 '15 at 21:44
  • The question is almost the same except that the deserialization did not work. Hence I tried to make an other question with an updated and different code. Actually the `Ptr` is not from me and is used by other users so I do not know how they will use it. So I do not know how to implement the serialization following your philosophy and this solution seemed to work for my needs. I would like to take Richard's solution but unfortunately and I do not why it does not work in some cases see the live codes I answer in comments. – coincoin Aug 04 '15 at 23:24
  • you should work out the _semantics_ of the `Ptr<>` type. Specifically the question whether `Ptr` could/should alias and present unique identity (how do you want things to be tracked). That's the whole issue here. Don't paper over things with more code. It'll never do what you need it until you figure out what it is, you need – sehe Aug 04 '15 at 23:43