Повысить сериализацию класса, обрабатывающего возможный нулевой указатель

Я хочу сериализовать следующий класс, оборачивая указатель, который может обрабатывать нулевой m_element, как вы можете видеть при вызове конструктора по умолчанию. Это следует за этим вопросом.

Живой MCVE на Колиру

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);

    }
}

Сериализация, кажется, работает, но десериализация дает ошибку. Я работаю в C++0x.

  • Как я могу обеспечить безопасное сохранение и загрузку функций для сериализации Ptr без изменения Ptr, если это возможно?
  • Если мне нужно изменить Ptr, что вы предлагаете?

Редактировать: благодаря комментарию Jarod42 я придумал следующие функции сохранения / загрузки, используя логическое значение для определения нулевого указателя или нет. Теперь у меня нет segfault больше, когда m_elem является нулевым, но у меня есть один, когда он не является нулевым.

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);
    }
}

2 ответа

Решение

Благодаря комментарию Jarod42,

Вы должны сериализовать логическое значение, чтобы узнать, является ли указатель nullptr или нет (и если нет, также сериализовать его содержимое). для загрузки извлеките это логическое значение и выделите элемент по умолчанию при необходимости, который вы загружаете.

мы пришли со следующими функциями сохранения и загрузки:

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);
    }
}

Жить на Колиру

Возникла проблема в функции загрузки, когда is_null имеет значение false. В этом случае для ptr действительно требуется хранилище.

Методы сохранения и загрузки boost::archive понимают разницу между указателями и ссылками на объекты. Вам не нужно указывать *m_elem. m_elem будет делать (и работать правильно). Boost поймет, является ли указатель нулевым, и просто сохранит значение, указывающее нулевой указатель, который будет десериализован правильно.

(упрощенный) пример:

#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;
    }
}

ожидаемый результат:

{nullptr}
A{6}
{nullptr}
A{6}
Другие вопросы по тегам