Как можно использовать boost :: serialization с std :: shared_ptr из C ++ 11?

Я знаю, что существует модуль Boost для сериализация _ 1_, но я не могу найти ничего для _ 2_.

Кроме того, я не знаю, как это легко реализовать. Боюсь, что следующий код

namespace boost{namespace serialization{
template<class Archive, class T>
inline void serialize(Archive & ar, std::shared_ptr<T> &t, const unsigned int version)
{
  if(Archive::is_loading::value) {T*r;ar>>r;t=r;}
  else {ar<<t.get();}
}
}}//namespaces

не работает. Действительно, если к какому-либо объекту обращались несколько раз, он был бы загружен при первом запуске ar>>r, а после этого будет скопирован только указатель. Однако мы создадим несколько shared_ptr объектов, указывающих на него, и, следовательно, уничтожим его более одного раза.

Есть идеи по этому поводу?

Некоторые технические подробности о системе, которую я использую:

  • ОС: Ubuntu 11.10 (x64)
  • Компилятор: g ++ (Ubuntu / Linaro 4.6.1-9ubuntu3) 4.6.1
  • версия boost: 1.46.1 (устанавливается с sudo apt-get install libboost-dev)

person fiktor    schedule 13.11.2011    source источник
comment
Однако мы создадим несколько объектов shared_ptr, указывающих на него, и, следовательно, уничтожим его более одного раза. Я думаю, что это то, с чем вы должны иметь дело при сериализации. Нет автоматизированного решения, которое могло бы знать, что некоторый shared_ptr (или обычный указатель, если на то пошло), который был сериализован ранее, указывает на тот же объект.   -  person Nicol Bolas    schedule 15.11.2011
comment
@NicolBolas: одна из целей повышения: сериализация - это сериализация контейнеров STL и других часто используемых шаблонов. std :: shared_ptr является частью стандартной библиотеки C ++ 11. Это заставляет меня поверить, что сериализация std :: shared_ptr где-то уже реализована (может быть, в библиотеке boost (?) Или где-то еще), но я просто не смог ее найти.   -  person fiktor    schedule 15.11.2011
comment
C ++ 11, как следует из названия, вышел в этом году. Действительно, спецификация была доработана всего несколько месяцев назад. И хотя GCC и Clang поддерживают его большую часть, ничто не поддерживает все. Конечно, он еще не полностью поддерживается; как вы могли этого ожидать? И даже тогда это не меняет того факта, что он не может магическим образом узнать, что две вещи, сериализованные в разное время, на самом деле относятся к одному и тому же объекту. Это так же верно как для "голых" указателей, так и для "умных" указателей. Вам решать, как справиться с этим преобразованием.   -  person Nicol Bolas    schedule 15.11.2011
comment
@NicolBolas: Хорошо, я вижу, что C ++ 11 является новым, а std :: shared_ptr, возможно, еще не поддерживается boost. Однако я не могу согласиться с вашим вторым тезисом: довольно умный сериализация указателей реализована в boost :: serialization и то же самое касается boost :: shared_ptr.   -  person fiktor    schedule 15.11.2011
comment
Я совсем не boost::serialization эксперт, но пробовали ли вы скопировать boost/serialization/shared_ptr.hpp, на который вы ссылаетесь, и заменить все boost::shared_ptr на std::shared_ptr?   -  person Jeffrey Yasskin    schedule 16.12.2011
comment
@NicolBolas Нет, boost :: serialization действительно управляет несколькими указателями на один объект. Голые и умные указатели. Никакой магии не требуется.   -  person Drew Dormann    schedule 08.01.2012


Ответы (7)


Начиная с Boost 1.56 библиотека сериализации имеет встроенная поддержка std :: shared_ptr. Вам не нужно реализовывать собственные вспомогательные функции сериализации, если вы можете использовать более новую версию библиотеки.

person joshuanapoli    schedule 17.01.2015
comment
Это правильный ответ. Это только должно быть завершено: добавьте #include <boost/serialization/shared_ptr.hpp>, чтобы использовать его! - person herchu; 23.11.2016
comment
А как насчет unique_ptr? Мне нужно поменять все мои unique_ptr на shared_ptr? - person Matthieu H; 29.11.2019
comment
Нет, вам не нужно изменять unique_ptr; он поддерживается библиотекой ускоренной сериализации. См. stackoverflow.com/a/58045763/766900 - person joshuanapoli; 29.11.2019

Я наконец нашел решение о том, как сериализовать std :: shared_ptr с помощью ускоренной сериализации. Все, что вам нужно, это следующий фрагмент кода (объяснение следует ниже):

#include <boost/serialization/split_free.hpp>
#include <boost/unordered_map.hpp>

//---/ Wrapper for std::shared_ptr<> /------------------------------------------

namespace boost { namespace serialization {

template<class Archive, class Type>
void save(Archive & archive, const std::shared_ptr<Type> & value, const unsigned int /*version*/)
{
    Type *data = value.get();
    archive << data;
}

template<class Archive, class Type>
void load(Archive & archive, std::shared_ptr<Type> & value, const unsigned int /*version*/)
{
    Type *data;
    archive >> data;

    typedef std::weak_ptr<Type> WeakPtr;
    static boost::unordered_map<void*, WeakPtr> hash;

    if (hash[data].expired())
    {
         value = std::shared_ptr<Type>(data);
         hash[data] = value;
    }
    else value = hash[data].lock();
}

template<class Archive, class Type>
inline void serialize(Archive & archive, std::shared_ptr<Type> & value, const unsigned int version)
{
    split_free(archive, value, version);
}

}}

Этот код просто сериализует объект, управляемый std :: shared_ptr в функции save (). Если несколько экземпляров std :: shared_ptr указывают на один и тот же объект, ускоренная сериализация автоматически позаботится о том, чтобы сохранить его только один раз. Магия происходит в load (), где ускоренная сериализация возвращает необработанный указатель на объект (данные). Этот необработанный указатель ищется в хэше, который содержит weak_ptr для каждого необработанного указателя. В случае, если срок действия weak_ptr в хэше истек, мы можем безопасно создать новый экземпляр shared_ptr, позволить ему управлять необработанным указателем и сохранить weak_ptr в хеше. В случае, если срок действия weak_ptr не истек, мы просто блокируем его, чтобы вернуть shared_ptr. Таким образом, подсчет ссылок будет правильным.

person denim    schedule 21.02.2013
comment
Я думаю, что лучше использовать обычный std :: map. Кроме того, возможно, что weak_ptr истечет между его проверкой и вызовом lock (). Вместо этого проверьте результат lock (). Тем не менее голосование "за". - person Brent; 06.04.2013
comment
Этот код выглядит странно: archive << data; здесь вы только сериализуете указатель и снова загружаете его в load. но где сериализованы данные, на которые указывает datapoints ??? и, кроме того, статическая карта hash может увеличиваться до тех пор, пока не закончится память, нет удаления записей ... - person Gabriel; 23.06.2014
comment
Вы уверены, что hash необходим? Во-первых, это почти утечка памяти, потому что хэш будет увеличиваться даже в длинной программе (?). Во-вторых, я протестировал код, и простое использование value = std::shared_ptr<Type>(data) (вместо блока if) работает и (что важно) десериализованные объекты указывают на одну и ту же память, как если бы архив уже позаботился о параллельном указателе. Я что-то упускаю? - person alfC; 26.08.2014

Сериализация обеспечивается ускорением, а не стандартной библиотекой, и хотя shared_ptr включен в стандарт, он является частью TR1 (технический отчет 1).

TR1 на данный момент не имеет сериализации. Поэтому я бы порекомендовал вам использовать общий указатель boost.

person ken    schedule 17.02.2012

Вы не сказали, что означает «не работает»; он не компилируется? Он не загружает / не сохраняет значение должным образом? Это не ... что?

Здесь я могу выделить две проблемы, хотя одна может быть частью вашего намеренного дизайна.

Во-первых, вы не сделали правильный указатель в процедуре загрузки. Давайте разберемся:

inline void serialize(Archive & ar, std::shared_ptr<T> &t, const unsigned int version) {
    if (1) { //unimportant
        T* r;
        ar >> r;
        t = r;
    }
}

Когда вы создаете объект из std :: shared_ptr, вы создаете экземпляр шаблона класса, чтобы обеспечить возможность, подобную указателю (как вы знаете). Если вы сделали с int, он будет работать как указатель int. Однако простая передача типа как T НЕ означает, что указатель, созданный для этого типа, будет автоматически использовать этот шаблон; действительно, вы создаете пустой указатель с помощью T * r. С таким же успехом это может быть int * r. Затем вы не можете инициализировать его с помощью new; r может указывать куда угодно. Если он был инициализирован правильно с новым, вы МОЖЕТЕ получить правильный подсчет ссылок для создания / удаления этого объекта; это одна из областей, где std :: shared_ptr мне кажется не стоит усилий. Я думаю, что присвоение голого указателя считается второй ссылкой, а не первой, но я могу ошибаться? Во всяком случае, проблема не в этом. Вы, вероятно, портите кучу; компилятор должен выдавать предупреждение об использовании неинициализированного указателя, удивительно, что этого не произошло. Надеюсь, у вас не отключены предупреждения.

Если я правильно помню, это объявление r нужно заменить на:

std::shared_ptr<T> r = new std::shared_ptr<T>;

Хотя может быть

std::shared_ptr<T> r = new std::shared_ptr<T>(r());

Я давно не использовал shared_ptr.

TR1, кстати, отсутствует как минимум 2 года. Он основан на увеличении shared_ptr. Я не знаю, почему вы используете Boost 1.46, но я думаю, что его уже не было к тому времени, когда shared_ptr стал частью стандарта? Так должно быть совместимо ...?

Во всяком случае, вторая потенциальная ошибка связана с

t = r;

Я предполагаю - неправильно? - что вы ХОТИТЕ уменьшить счетчик ссылок до t, переназначив его (и, возможно, уничтожив объект, на который указывает t). Если вы хотели скопировать его, вы, конечно, использовали бы:

*t = *r;

и убедитесь, что ваш конструктор копирования работает правильно.

person std''OrgnlDave    schedule 12.01.2012
comment
О, один комментарий. Boost 1.46 также может быть несовместим с новым C ++ 1x std :: shared_ptr, потому что он отличается от std :: tr1 :: shared_ptr, который, я думаю, был использован для Boost 1.46. - person std''OrgnlDave; 12.01.2012

Последние версии Boost Serialization включают поддержку всех стандартных интеллектуальных указателей библиотеки.

person Robert Ramey    schedule 24.11.2015

Это усовершенствование решения denim, которое поддерживает загрузку shared_ptr, указывающую на одну и ту же память, но с разными типами. Эта проблема может возникнуть, если архив содержит одновременно shared_ptr и shared_ptr, которые указывают на один и тот же объект, где A наследуется от B.

namespace boost {
namespace serialization {

    template<class Archive, class Type>
    void save(Archive & archive, const std::shared_ptr<Type> & value, const unsigned int /*version*/)
    {
        Type *data = value.get();
        archive << data;
    }

    static std::map<void*, std::weak_ptr<void>> hash;

    template<class Archive, class Type>
    void load(Archive & archive, std::shared_ptr<Type> & value, const unsigned int /*version*/)
    {
        Type *data;
        archive >> data;

        if (hash[data].expired())
        {
            std::shared_ptr<void> ptr(data);
            value = static_pointer_cast<Type>(ptr);
            hash[data] = ptr;
        }
        else value = static_pointer_cast<Type>(hash[data].lock());
    }

    template<class Archive, class Type>
    inline void serialize(Archive & archive, std::shared_ptr<Type> & value, const unsigned int version)
    {
        split_free(archive, value, version);
    }

}}

Как слабое место этой реализации - одна массивная карта.

person Bogdan Mazurenko    schedule 25.12.2014

Это результат сворачивания вашего собственного на основе заголовка общего указателя boost, например. на основе <boost/serialization/shared_ptr.hpp>.

Просто скопируйте и вставьте ниже в файл заголовка и включите его:

#ifndef BOOST_SERIALIZATION_STD_SHARED_PTR_HPP
#define BOOST_SERIALIZATION_STD_SHARED_PTR_HPP

// MS compatible compilers support #pragma once
#if defined(_MSC_VER) && (_MSC_VER >= 1020)
# pragma once
#endif

/////////1/////////2/////////3/////////4/////////5/////////6/////////7/////////8
// shared_ptr.hpp: serialization for boost shared pointer

// (C) Copyright 2004 Robert Ramey and Martin Ecker
// Use, modification and distribution is subject to the Boost Software
// License, Version 1.0. (See accompanying file LICENSE_1_0.txt or copy at
// http://www.boost.org/LICENSE_1_0.txt)

//  See http://www.boost.org for updates, documentation, and revision history.

#include <cstddef> // NULL

#include <boost/config.hpp>
#include <boost/mpl/integral_c.hpp>
#include <boost/mpl/integral_c_tag.hpp>

#include <boost/detail/workaround.hpp>
#include <memory>

#include <boost/serialization/split_free.hpp>
#include <boost/serialization/nvp.hpp>
#include <boost/serialization/version.hpp>
#include <boost/serialization/tracking.hpp>

/////////1/////////2/////////3/////////4/////////5/////////6/////////7/////////8
// shared_ptr serialization traits
// version 1 to distinguish from boost 1.32 version. Note: we can only do this
// for a template when the compiler supports partial template specialization

#ifndef BOOST_NO_TEMPLATE_PARTIAL_SPECIALIZATION
    namespace boost {
    namespace serialization{
        template<class T>
        struct version< ::std::shared_ptr< T > > {
            typedef mpl::integral_c_tag tag;
            #if BOOST_WORKAROUND(__MWERKS__, BOOST_TESTED_AT(0x3206))
            typedef BOOST_DEDUCED_TYPENAME mpl::int_<1> type;
            #else
            typedef mpl::int_<1> type;
            #endif
            #if BOOST_WORKAROUND(__BORLANDC__, BOOST_TESTED_AT(0x570))
            BOOST_STATIC_CONSTANT(int, value = 1);
            #else
            BOOST_STATIC_CONSTANT(int, value = type::value);
            #endif
        };
        // don't track shared pointers
        template<class T>
        struct tracking_level< ::std::shared_ptr< T > > {
            typedef mpl::integral_c_tag tag;
            #if BOOST_WORKAROUND(__MWERKS__, BOOST_TESTED_AT(0x3206))
            typedef BOOST_DEDUCED_TYPENAME mpl::int_< ::boost::serialization::track_never> type;
            #else
            typedef mpl::int_< ::boost::serialization::track_never> type;
            #endif
            #if BOOST_WORKAROUND(__BORLANDC__, BOOST_TESTED_AT(0x570))
            BOOST_STATIC_CONSTANT(int, value = ::boost::serialization::track_never);
            #else
            BOOST_STATIC_CONSTANT(int, value = type::value);
            #endif
        };
    }}
    #define BOOST_SERIALIZATION_SHARED_PTR(T)
#else
    // define macro to let users of these compilers do this
    #define BOOST_SERIALIZATION_SHARED_PTR(T)                         \
    BOOST_CLASS_VERSION(                                              \
        ::std::shared_ptr< T >,                                     \
        1                                                             \
    )                                                                 \
    BOOST_CLASS_TRACKING(                                             \
        ::std::shared_ptr< T >,                                     \
        ::boost::serialization::track_never                           \
    )                                                                 \
    /**/
#endif

namespace boost {
namespace serialization{

#ifndef BOOST_SERIALIZATION_SHARED_PTR_HPP
struct null_deleter {
    void operator()(void const *) const {}
};
#endif

/////////1/////////2/////////3/////////4/////////5/////////6/////////7/////////8
// serialization for shared_ptr

template<class Archive, class T>
inline void save(
    Archive & ar,
    const std::shared_ptr< T > &t,
    const unsigned int /* file_version */
){
    // The most common cause of trapping here would be serializing
    // something like shared_ptr<int>.  This occurs because int
    // is never tracked by default.  Wrap int in a trackable type
    BOOST_STATIC_ASSERT((tracking_level< T >::value != track_never));
    const T * t_ptr = t.get();
    ar << boost::serialization::make_nvp("px", t_ptr);
}

template<class Archive, class T>
inline void load(
    Archive & ar,
    std::shared_ptr< T > &t,
    const unsigned int /*file_version*/
){
    // The most common cause of trapping here would be serializing
    // something like shared_ptr<int>.  This occurs because int
    // is never tracked by default.  Wrap int in a trackable type
    BOOST_STATIC_ASSERT((tracking_level< T >::value != track_never));
    T* r;
    ar >> boost::serialization::make_nvp("px", r);
    ar.reset(t,r);
}

template<class Archive, class T>
inline void serialize(
    Archive & ar,
    std::shared_ptr< T > &t,
    const unsigned int file_version
){
    // correct shared_ptr serialization depends upon object tracking
    // being used.
    BOOST_STATIC_ASSERT(
        boost::serialization::tracking_level< T >::value
        != boost::serialization::track_never
    );
    boost::serialization::split_free(ar, t, file_version);
}

} // namespace serialization
} // namespace boost

#endif // BOOST_SERIALIZATION_STD_SHARED_PTR_HPP

Вы можете просмотреть отличия от <boost/serialization/shared_ptr.hpp> здесь

По сути,

  • переименован включить охранник
  • изменил boost::shared_ptr на std::shared_ptr
  • включены <memory> вместо <boost/shared_ptr.hpp>
  • защищен null_deleter от переопределения, если вы также используете boost::shared_ptr
  • удалил BOOST_SERIALIZATION_SHARED_PTR_132_HPP - о чем идет речь?

Пока вроде все работает нормально.

person Cookie    schedule 18.02.2015
comment
если используемая вами версия boost не поддерживает сериализацию shared_ptr, как она может реализовать функцию ar.reset(t,r);? - person iggy; 05.11.2015
comment
Я думаю, вы неправильно поняли. Это сериализует std :: shared_ptr с ускоренной сериализацией до 1.56. Поскольку boost 1.56 поддерживает сам std :: shared_ptr. - person Cookie; 05.11.2015
comment
но в архиве до 1.56 не может быть функции ar.reset(t,r);, где t - shared_ptr - person iggy; 05.11.2015