Карта сайта Kansoftware
НОВОСТИУСЛУГИРЕШЕНИЯКОНТАКТЫ
Разработка программного обеспечения

Serialization - Serialization of Classes

Boost , ,

C++ Boost

Serialization

Serializable Concept


Primitive Types
Class Types
Member Function
Free Function
Namespaces for Free Function Overrides
Class Members
Base Classes
const Members
Templates
Versioning
Splitting serialize into save/load
Member Functions
Free Functions
Pointers
Non-Default Constructors
Pointers to Objects of Derived Classes
Registration
Export
Instantiation
Selective Tracking
Runtime Casting
References
Arrays
Class Serialization Traits
Serialization Wrappers
Models - Serialization Implementations Included in the Library
A type T is Serializable if and only if one of the following is true:
  • it is a primitive type.
    By primitive type we mean a C++ built-in type and ONLY a C++ built-in type. Arithmetic (including characters), bool, enum are primitive types. Below in serialization traits, we define a "primitive" implementation level in a different way for a different purpose. This can be a source of confusion.
  • It is a class type and one of the following has been declared according to the prototypes detailed below:
    • a class member function serialize
    • a global function serialize
  • it is a pointer to a Serializable type.
  • it is a reference to a Serializable type.
  • it is a native C++ Array of Serializable type.

Primitive Types

The template operators &, <<, and >> of the archive classes described above will generate code to save/load all primitive types to/from an archive. This code will usually just add the data to the archive according to the archive format. For example, a four byte integer is appended to a binary archive as 4 binary bytes while a to a text archive it would be rendered as a space followed by a string representation.

Class Types

For class/struct types, the template operators &, <<, and >> will generate code that invokes the programmer's serialization code for the particular data type. There is no default. An attempt to serialize a class/struct for which no serialization has been explicitly specified will result in a compile time error. The serialiation of a class can be specified via either a class member function or a free funcation which takes a reference to an instance of the class as an argument.

Member Function

The serialization library invokes the following code to save or load a class instance to/from and archive.

template<class Archive, class T>
inline void serialize(
    Archive & ar, 
    T & t, 
    const unsigned int file_version
){
    // invoke member function for class T
    t.serialize(ar, file_version);
}
That is, the default definition of template serialize presumes the existence of a class member function template of the following signature:

template<class Archive>
void serialize(Archive &ar, const unsigned int version){
    ...
}
If such a member function is not declared, a compile time error will occur. In order that the member function generated by this template can be called to append the data to an archive, it either must be public or the class must be made accessible to the serialization library by including:

friend class boost::serialization::access;
in the class definition. This latter method should be preferred over the option of making the member function public. This will prevent serialization functions from being called from outside the library. This is almost certainly an error. Unfortunately, it may appear to function but fail in a way that is very difficult to find.

Может быть не сразу очевидно, как этот шаблон служит как для сохранения данных в архив, так и для загрузки данных из архива. Ключ в том, что оператор<&>определяется как<<<>для выходных архивов и как<>>>входных архивов. «Полиморфное» поведение<&>позволяет использовать один и тот же шаблон для операций сохранения и загрузки. Это очень удобно в том смысле, что экономит массу набора текста и гарантирует, что сохранение и загрузка классовых данных всегда синхронизированы. Это ключ ко всей системе сериализации.

Свободная функция

Конечно, мы не ограничены использованием реализации по умолчанию, описанной выше. Мы можем преодолеть дефолт с нашим собственным. Это позволит нам осуществить сериализацию класса без изменения самого определения класса. Мы называем этоненавязчивойсериализацией. Допустим, что наш класс называется<my_class>, оверрайд будет определен как:<

// namespace selection
template<class Archive>
inline void serialize(
    Archive & ar, 
    my_class & t, 
    const unsigned int file_version
){
    ...
}
>Обратите внимание, что мы назвали этот оверрайд «ненавязчивым». Это несколько неточно. Не требуется, чтобы класс имел специальные функции, чтобы он был получен из какого-либо общего базового класса или каких-либо других фундаментальных изменений дизайна. Тем не менее, это потребует доступа к членам класса, которые должны быть сохранены и загружены. Если эти члены<private>, их не удастся сериализовать. Поэтому в некоторых случаях даже при использовании этого «ненавязчивого» метода будут необходимы незначительные модификации класса, подлежащие сериализации. На практике это может быть не такой проблемой, поскольку многие библиотеки (E.G. STL) предоставляют достаточно информации, чтобы разрешить реализацию ненавязчивой сериализации без каких-либо изменений в библиотеке.

Пространства имен для переопределения свободных функций

Для максимальной переносимости, включите любые бесплатные шаблоны функций и определения в пространстве имен<boost::serialization>. Если переносимость не является проблемой, и используемый компилятор поддерживает ADL (Argument Dependent Lookup), бесплатные функции и шаблоны могут быть в любом из следующих пространств имен:
  • <boost::serialization>
  • пространство имен класса архива
  • пространство имен типа, сериализуемого

Обратите внимание, что на первый взгляд это предположение может показаться неправильным для компиляторов, которые реализуют двухфазный поиск. На самом деле библиотека сериализации использовала, возможно, слишком умный метод для поддержки этого правила даже для таких компиляторов. Те, кто заинтересован в дальнейшем изучении этого, найдут больше информации вserialization.hpp

Сериализация членов класса

Независимо от того, какой из вышеперечисленных методов используется, в корпусе сериализованной функции должны указываться данные, подлежащие сохранению/загрузке путем последовательного применения архива<operator &>ко всем членам данных класса.<

{
    // save/load class member variables
    ar & member1;
    ar & member2;
}
>

Базовые классы

Файл заголовкаbase_object.hppвключает шаблон:<

template<class Base, class Derived>
Base & base_object(Derived &d);
>, который должен использоваться для создания ссылки на объект базы, который может использоваться в качестве аргумента для операторов сериализации архива. Так для классаСериализируемоесостояние<T>базового класса должно быть сериализовано следующим образом:<

{
    // invoke serialization of the base class 
    ar & boost::serialization::base_object<base_class_of_T>(*this);
    // save/load class member variables
    ar & member1;
    ar & member2;
}
>Сопротивляйтесь искушению просто бросить<*this>в базовый класс. Это может показаться эффективным, но может не вызвать код, необходимый для правильной сериализации.

Обратите внимание, что этоНЕто же самое, что называть<serialize>функцией базового класса. Может показаться, что это работает, но обойдет определенный код, используемый для отслеживания объектов, и регистрации отношений, основанных на базе, и другой бухгалтерии, которая необходима для того, чтобы система сериализации функционировала так, как задумано. По этой причине все<serialize>членские функции должны быть<private>.

<const>Члены

Спасение<const>членов архива не требует особых соображений. Загрузка<const>членов может осуществляться с помощью<const_cast>:<

    ar & const_cast<T &>(t);
>Обратите внимание, что это нарушает дух и намерение ключевого слова<const>.<const>Члены интиализируются при построении классового экземпляра и не изменяются после этого. Однако это может быть наиболее уместным во многих случаях. В конечном счете, это сводится к вопросу о том, что<const>означает в контексте сериализации.

Шаблоны

Внедрение сериализации для шаблонов является точно таким же процессом, как и для обычных классов, и не требует дополнительных соображений. Среди прочего, это означает, что сериализация композиций шаблонов автоматически генерируется при необходимости, если определена сериализация компонентных шаблонов. Например, эта библиотека включает определение сериализации для<boost::shared_ptr<T>>и для<std::list<T>>. Если я определил сериализацию для своего класса<my_t>, то сериализация для<std::list< boost::shared_ptr< my_t> >>уже доступна для использования.

Пример, который показывает, как эта идея может быть реализована для ваших шаблонов классов, см.demo_auto_ptr.cpp. Это показывает, как может быть реализована ненавязчивая сериализация для шаблона<auto_ptr>из стандартной библиотеки.

Несколько более сложное добавление сериализации к стандартному шаблону можно найти в примереshared_ptr.hpp

В спецификации сериализации для шаблонов принято делить<serialize>на<load/save>пару. Обратите внимание, что макрос удобства, описанныйвыше, не полезен в этих случаях, поскольку число и тип аргументов класса шаблонов не будут соответствовать тем, которые используются при разделении<serialize>для простого класса. Вместо этого используйте синтаксис override.

Версия

В конечном итоге после создания архивов определения классов изменятся. При сохранении экземпляра класса текущая версия включается в информацию класса, хранящуюся в архиве. Когда экземпляр класса загружается из архива, исходный номер версии передается в качестве аргумента функции загрузки. Это позволяет функции нагрузки включать логику для размещения старых определений для класса и согласования их с последней версией. Функции сохранения всегда сохраняют текущую версию. Это приводит к автоматическому преобразованию архивов старых форматов в новейшие версии. Номера версий поддерживаются независимо для каждого класса. Это приводит к простой системе для доступа к старым файлам и их конверсии. Текущая версия класса присваивается какКласс Сериализации Тит, описанный позже в этом руководстве.<

{
    // invoke serialization of the base class 
    ar & boost::serialization::base_object<base_class_of_T>(*this);
    // save/load class member variables
    ar & member1;
    ar & member2;
    // if its a recent version of the class
    if(1 < file_version)
        // save load recently added class members
        ar & member3;
}
>

Расщепление<serialize>в Сохранить/Загрузить

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

Разделение функций

Для функций-членов это может быть решено путем включения файла заголовкаboost/serialization/split_member.hpp, включая такой код в классе:<

template<class Archive>
void save(Archive & ar, const unsigned int version) const
{
    // invoke serialization of the base class 
    ar << boost::serialization::base_object<const base_class_of_T>(*this);
    ar << member1;
    ar << member2;
    ar << member3;
}
template<class Archive>
void load(Archive & ar, const unsigned int version)
{
    // invoke serialization of the base class 
    ar >> boost::serialization::base_object<base_class_of_T>(*this);
    ar >> member1;
    ar >> member2;
    if(version > 0)
        ar >> member3;
}
template<class Archive>
void serialize(
    Archive & ar,
    const unsigned int file_version 
){
    boost::serialization::split_member(ar, *this, file_version);
}
>Это разделяет сериализацию на две отдельные функции<save>и<load>. Так как новый шаблон<serialize>всегда одинаков, он может быть создан с помощью макроса. BOOST_SERIALIZATION_SPLIT_MEMBER() определено в файле заголовкаboost/serialization/split_member.hpp. Таким образом, вся функция<serialize>выше может быть заменена:<

BOOST_SERIALIZATION_SPLIT_MEMBER()
>

Расщепление свободных функций

Такая же ситуация и с ненавязчивой сериализацией со свободным<serialize>шаблоном функций.Чтобы использовать<save>и<load>шаблоны функций, а не<serialize>:<

namespace boost { namespace serialization {
template<class Archive>
void save(Archive & ar, const my_class & t, unsigned int version)
{
    ...
}
template<class Archive>
void load(Archive & ar, my_class & t, unsigned int version)
{
    ...
}
}}
>, включите файл заголовка
boost/serialization/split_free.hppи переопределите бесплатный шаблон функции<serialize>:<

namespace boost { namespace serialization {
template<class Archive>
inline void serialize(
    Archive & ar,
    my_class & t,
    const unsigned int file_version
){
    split_free(ar, t, file_version); 
}
}}
>Чтобы сократить набор текста, вышеуказанный шаблон можно заменить макросом:<

BOOST_SERIALIZATION_SPLIT_FREE(my_class)
>Обратите внимание, что, хотя функция разделения функции< serialize>на<save/load>была предоставлена, предпочтительно использование функции<serialize>с соответствующим оператором<&>. Ключом к реализации сериализации является то, что объекты сохраняются и загружаются в точно такой же последовательности. Использование оператора<&>и функции<serialize>гарантирует, что это всегда так и минимизирует возникновение трудно обнаруживаемых ошибок, связанных с синхронизацией функций<save>и<load>.

Также обратите внимание, что<BOOST_SERIALIZATION_SPLIT_FREE>должно использоваться за пределами любого пространства имен.

Указатели

Указатель на любой экземпляр класса может быть сериализован с любым из операторов сохранения/загрузки архива.

Для правильного сохранения и восстановления объекта с помощью указателя необходимо рассмотреть следующие ситуации:

  1. Если один и тот же объект сохраняется несколько раз через разные указатели, необходимо сохранить только одну копию объекта.
  2. Если объект загружается несколько раз через разные указатели, то должен быть создан только один новый объект и все возвращенные указатели должны указывать на него.
  3. Система должна обнаружить случай, когда объект сначала сохраняется через указатель, а затем сам объект сохраняется. Без принятия дополнительных мер предосторожности загрузка приведет к созданию нескольких копий исходного объекта. Эта система выявляет этот случай при экономии и бросает исключение — см. ниже.
  4. Объект производного класса может храниться через указатель на базовый класс. Истинный тип объекта должен быть определен и сохранен. При восстановлении необходимо создать правильный тип и правильно отлить его адрес в базовый класс. То есть нужно учитывать полиморфные указатели.
  5. Указатели NULL должны быть вычтены при сохранении и восстановлены в NULL при десериализации.
Эта библиотека сериализации учитывает все вышеперечисленные соображения. Процесс сохранения и загрузки объекта через указатель нетривиален. Его можно резюмировать следующим образом:

Сохранение указателя:

  1. определяют истинный тип указываемого объекта.
  2. напишите специальный тег в архив
  3. , если указанный объект еще не был записан в архив, сделайте это сейчас
. Загрузка указателя:
  1. читайте тег из архива.
  2. определить тип создаваемого объекта
  3. , если объект уже загружен, вернуть его адрес.
  4. в противном случае, создать новый экземпляр объекта
  5. считывать данные обратно при использовании операторов, описанных выше
  6. возвращать адрес вновь созданного объекта.
Учитывая, что экземпляры классов сохраняются/загружаются в/из архива только один раз, независимо от того, сколько раз они сериализуются с операторами<<<>и<>>>
  • Загрузка одного и того же указательного объекта несколько раз приводит к созданию только одного объекта, тем самым копируя исходную конфигурацию указателя.
  • Структуры, такие как коллекции полиморфных указателей, обрабатываются без особых усилий со стороны пользователей этой библиотеки.
Сериализация указателей производных типов через указатель на базовый класс может потребовать немного дополнительной «помощи». Кроме того, программист может захотеть изменить описанный выше процесс по своим собственным причинам. Например, может быть желательно подавить отслеживание объектов, поскольку априори известно, что рассматриваемое приложение никогда не может создавать дублирующиеся объекты. Сериализация указателей может быть «тонко настроена» с помощью спецификацииКласса Сериализации признаков, как описано вдругом разделе этого руководства

Непо умолчанию Конструкторы

Сериализация указателей осуществляется в библиотеке с кодом, аналогичным следующему:<

// load data required for construction and invoke constructor in place
template<class Archive, class T>
inline void load_construct_data(
    Archive & ar, T * t, const unsigned int file_version
){
    // default just uses the default constructor to initialize
    // previously allocated memory. 
    ::new(t)T();
}
>По умолчанию<load_construct_data>вызывает конструктор по умолчанию «на месте» для инициализации памяти.

Если такого конструктора по умолчанию нет, шаблоны функций<load_construct_data>и, возможно,<save_construct_data>должны быть переопределены. Вот простой пример:<


class my_class {
private:
    friend class boost::serialization::access;
    const int m_attribute;  // some immutable aspect of the instance
    int m_state;            // mutable state of this instance
    template<class Archive>
    void serialize(Archive &ar, const unsigned int file_version){
        ar & m_state;
    }
public:
    // no default construct guarentees that no invalid object
    // ever exists
    my_class(int attribute) :
        m_attribute(attribute),
        m_state(0)
    {}
};
>переопределение будет:<

namespace boost { namespace serialization {
template<class Archive>
inline void save_construct_data(
    Archive & ar, const my_class * t, const unsigned int file_version
){
    // save data required to construct instance
    ar << t->m_attribute;
}
template<class Archive>
inline void load_construct_data(
    Archive & ar, my_class * t, const unsigned int file_version
){
    // retrieve data from archive required to construct new instance
    int attribute;
    ar >> attribute;
    // invoke inplace constructor to initialize instance of my_class
    ::new(t)my_class(attribute);
}
}} // namespace ...
>В дополнение к десериализации указателей, эти оверрайды используются в десериализации контейнеров STL, тип элемента которых не имеет конструктора по умолчанию.

Указатели на объекты производных классов

Регистрация

Рассмотрим следующее:<

class base {
    ...
};
class derived_one : public base {
    ...
};
class derived_two : public base {
    ...
};
main(){
    ...
    base *b;
    ...
    ar & b; 
}
>При спасении<b>какой объект должен быть спасен? При загрузке<b>какой объект должен быть создан? Должен ли он быть объектом класса<derived_one>,<derived_two>или, может быть,<base>?

Получается, что вид сериализованного объекта зависит от того, является ли базовый класс (<base>в данном случае) полимофичным или нет. Если<base>не полиморфно, то есть не имеет виртуальных функций, то объект типа<base>будет сериализован. Информация в любом производном классе будет потеряна. Если это то, что желательно (обычно это не так), то никаких других усилий не требуется.

Если базовый класс полиморфен, то объект наиболее производного типа<derived_one>или<derived_two>в этом случае будет сериализован. Вопрос о том, какой тип объекта должен быть сериализован (почти) автоматически решается библиотекой.

Система «регистрирует» каждый класс в архиве, когда объект этого класса сериализуется и присваивает ему последовательное число. В следующий раз, когда объект этого класса будет сериализован в том же архиве, этот номер будет записан в архиве. Таким образом, каждый класс однозначно идентифицируется в архиве. При повторном прочтении архива каждый новый порядковый номер повторно связывается с прочитанным классом. Обратите внимание, что это означает, что «регистрация» должна происходить как во время сохранения, так и при нагрузке, так что таблица с целым классом, построенная на нагрузке, идентична таблице с целым классом, построенной на экономии. На самом деле, ключ ко всей системе сериализации заключается в том, что вещи всегда сохраняются и загружаются в одной последовательности. Это включает в себя «регистрацию».

Расширение нашего предыдущего примера:<


main(){
    derived_one d1;
    derived_two d2:
    ...
    ar & d1;
    ar & d2;
    // A side effect of serialization of objects d1 and d2 is that
    // the classes derived_one and derived_two become known to the archive.
    // So subsequent serialization of those classes by base pointer works
    // without any special considerations.
    base *b;
    ...
    ar & b; 
}
>При чтении<b>ему предшествует уникальный (для архива) идентификатор класса, который ранее был связан с классом<derived_one>или<derived_two>.

Если производный класс НЕ был автоматически «зарегистрирован», как описано выше,<unregistered_class>Исключение будет брошено при вызове сериализации.

Это может быть решено путем явной регистрации производного класса. Все архивы получены из базового класса, который реализует следующий шаблон:<


template<class T>
register_type();
>Таким образом, наша проблема может быть решена путем написания:<

main(){
    ...
    ar.template register_type<derived_one>();
    ar.template register_type<derived_two>();
    base *b;
    ...
    ar & b; 
}
>Обратите внимание, что если функция сериализации разделена между сохранением и нагрузкой, обе функции должны включать регистрацию. Это необходимо для сохранения сохранения и соответствующей нагрузки в синхронизации.

Экспорт

Вышеизложенное будет работать, но может быть неудобным. Мы не всегда знаем, какие производные классы мы собираемся сериализовать, когда мы пишем код для сериализации через указатель базового класса. Каждый раз, когда пишется новый производный класс, мы должны возвращаться во все места, где базовый класс сериализуется и обновлять код.

Итак, у нас есть другой метод:<


#include <boost/serialization/export.hpp>
...
BOOST_CLASS_EXPORT_GUID(derived_one, "derived_one")
BOOST_CLASS_EXPORT_GUID(derived_two, "derived_two")
main(){
    ...
    base *b;
    ...
    ar & b; 
}
>Макро<BOOST_CLASS_EXPORT_GUID>связывает строку буквально с классом. В приведенном выше примере мы использовали струнный рендеринг имени класса. Если объект такого «экспортированного» класса сериализуется через указатель и в противном случае не регистрируется, строка «экспорт» включается в архив. После прочтения архива строка используется для поиска класса, который должен быть создан библиотекой сериализации. Это позволяет каждому классу быть в отдельном файле заголовка вместе с его идентификатором строки. Нет необходимости вести отдельную «предварительную регистрацию» производных классов, которые могут быть сериализованы. Этот метод регистрации называется «ключевым экспортом». Более подробную информацию по этой теме можно найти в разделе Классовые черты -Экспортный ключ.

Обоснование

Регистрация с помощью любого из вышеперечисленных методов выполняет другую роль, важность которой может быть неочевидна. Эта система опирается на шаблонные функции формы<template<class Archive, class T>>. Это означает, что код сериализации должен быть инстанцирован для каждой комбинации архива и типа данных, которые сериализованы в программе.

Полиморфные указатели производных классов никогда не могут быть объяснимы программой, поэтому обычно код для сериализации таких классов никогда не будет реализован. Таким образом, помимо включения строк экспортных ключей в архив,<BOOST_CLASS_EXPORT_GUID>явно инстанцирует код сериализации класса для всех классов архивов, используемых программой.

Селективное отслеживание

Отслеживается ли объект или нет, определяется егопризнаком отслеживания объекта. Настройка по умолчанию для определенных типов пользователей<track_selectively>. То есть отслеживать объекты тогда и только тогда, когда они сериализуются через указатели в любом месте программы. Предполагается, что любые объекты, которые «зарегистрированы» любым из вышеперечисленных средств, будут сериализованы через указатели где-то в программе и, следовательно, будут отслеживаться. В определенных ситуациях это может привести к неэффективности. Предположим, что у нас есть модуль класса, используемый несколькими программами. Поскольку некоторые программы сериализуют полиморфные указатели на объекты этого класса, мыэкспортируемидентификатор класса, указав<BOOST_CLASS_EXPORT>в заголовке класса. Когда этот модуль включен другой программой, объекты этого класса всегда будут отслеживаться, даже если в этом нет необходимости. Эту ситуацию можно решить, используя<track_never>в этих программах.

Может также случиться так, что даже если программа сериализуется через указатель, мы больше озабочены эффективностью, чем избегаем возможности создания дублирующихся объектов. Возможно, мы знаем, что дубликатов не будет. Также может быть, что создание нескольких дубликатов является доброкачественным и не стоит избегать, учитывая стоимость отслеживания дубликатов.<track_never>можно использовать.

Время выполнения Кастинг

Для правильного перевода между базовыми и производными указателями во время выполнения система требует, чтобы каждая базовая пара была найдена в таблице. Побочный эффект сериализации базового объекта с помощью<boost::serialization::base_object<Base>(Derived &)>состоит в том, чтобы гарантировать, что базовая/производная пара добавляется в таблицу до ввода функции<main>. Это очень удобно и приводит к чистому синтаксису. Единственная проблема заключается в том, что это может произойти, когда производный класс, сериализованный через указатель, не имеет необходимости вызывать сериализацию своего базового класса. В таком случае есть два варианта. Очевидным является вызов сериализации базового класса с<base_object>и указание пустой функции для сериализации базового класса. Альтернативой является «регистрация» отношений Base/Derived с использованием шаблона<void_cast_register<Derived, Base>();>. Обратите внимание, что это использование термина «регистр» не связано с его использованием в предыдущем разделе. Вот пример того, как это делается:<

#include <sstream>
#include <boost/serialization/serialization.hpp>
#include <boost/archive/text_iarchive.hpp>
#include <boost/serialization/export.hpp>
class base {
    friend class boost::serialization::access;
    //...
    // only required when using method 1 below
    // no real serialization required - specify a vestigial one
    template<class Archive>
    void serialize(Archive & ar, const unsigned int file_version){}
};
class derived : public base {
    friend class boost::serialization::access;
    template<class Archive>
    void serialize(Archive & ar, const unsigned int file_version){
        // method 1 : invoke base class serialization
        ar & boost::serialization::base_object<base>(*this);
        // method 2 : explicitly register base/derived relationship
        boost::serialization::void_cast_register<derived, base>(
            static_cast<derived *>(NULL),
            static_cast<base *>(NULL)
        )
    }
};
BOOST_CLASS_EXPORT_GUID(derived, "derived")
main(){
    //...
    std::stringstream ss;
    boost::archive::text_iarchive ar(ss);
    base *b;
    ar >> b; 
}
>

Для вызова этого шаблона в коде, составленном несоответствующими компиляторами, может использоваться следующий синтаксис:<


boost::serialization::void_cast_register(
    static_cast<Derived *>(NULL),
    static_cast<Base *>(NULL)
);
>Для получения дополнительной информации см.Синтаксис вызова шаблона

Ссылки

Классы, которые содержат эталонные элементы, обычно требуют конструкторов без по умолчанию, поскольку ссылки могут быть установлены только при построении экземпляра. Пример предыдущего раздела несколько сложнее, если в классе есть референтные члены. Это поднимает вопрос о том, как и где хранятся объекты, о которых идет речь, и как они создаются. Также возникает вопрос о ссылках на полиморфные базовые классы. В основном это те же вопросы, которые возникают относительно указателей. Это не удивительно, так как ссылки являются особым видом указателей. Мы решаем эти вопросы путем сериализации ссылок, как если бы они были указателями.<

class object;
class my_class {
private:
    friend class boost::serialization::access;
    int member1;
    object & member2;
    template<class Archive>
    void serialize(Archive &ar, const unsigned int file_version);
public:
    my_class(int m, object & o) :
        member1(m), 
        member2(o)
    {}
};
>Оверрайды будут:<

namespace boost { namespace serialization {
template<class Archive>
inline void save_construct_data(
    Archive & ar, const my_class * t, const unsigned int file_version
){
    // save data required to construct instance
    ar << t.member1;
    // serialize reference to object as a pointer
    ar << & t.member2;
}
template<class Archive>
inline void load_construct_data(
    Archive & ar, my_class * t, const unsigned int file_version
){
    // retrieve data from archive required to construct new instance
    int m;
    ar >> m;
    // create and load data through pointer to object
    // tracking handles issues of duplicates.
    object * optr;
    ar >> optr;
    // invoke inplace constructor to initialize instance of my_class
    ::new(t)my_class(m, *optr);
}
}} // namespace ...
>

Решетки

Если<T>является сериализуемым типом, то любой родной массив C++ типа T является сериализуемым типом. То есть, если<T>является сериализуемым типом, то автоматически доступно и будет функционировать следующее:<

T t[4];
ar << t;
    ...
ar >> t;
>

Класс Сериализации Черты

Обертки Сериализации

Модели - Реализации Сериализации В библиотеке

Описанные выше средства достаточны для осуществления сериализации всех контейнеров STL. На самом деле это было сделано и включено в библиотеку. Например, чтобы использовать включенный код сериализации для<std::list>, используйте:<

#include <boost/serialization/list.hpp>
>, а не<

#include <list>
>Поскольку первое включает в себя второе, это все, что необходимо. То же самое относится ко всем коллекциям STL, а также к шаблонам, необходимым для их поддержки (например,<std::pair>).

На момент написания этой статьи библиотека содержит сериализацию следующих классов повышения:

  • факультативный
  • вариант
  • scoped_ptr
  • shared_ptr
  • auto_ptr (демо)
Другие добавляются в список, поэтому проверьте раздел файлов повышения и заголовки для новых реализаций!

© CopyrightRobert Ramey2002-2004. Распространяется под лицензией Boost Software License, версия 1.0. (См. сопроводительный файл LICENSE_1_0.txt или копию по адресу http://www.boost.org/LICENSE_1_0.txt)

In the specification of serialization for templates, its common to split serialize into a load/save pair. Note that the convenience macro described above isn't helpful in these cases as the number and kind of template class arguments won't match those used when splitting serialize for a simple class. Use the override syntax instead.

Versioning

It will eventually occur that class definitions change after archives have been created. When a class instance is saved, the current version in included in the class information stored in the archive. When the class instance is loaded from the archive, the original version number is passed as an argument to the loading function. This permits the load function to include logic to accommodate older definitions for the class and reconcile them with latest version. Save functions always save the current version. So this results in automatically converting older format archives to the newest versions. Version numbers are maintained independently for each class. This results in a simple system for permitting access to older files and conversion of same. The current version of the class is assigned as a Class Serialization Trait described later in this manual.

{
    // invoke serialization of the base class 
    ar & boost::serialization::base_object<base_class_of_T>(*this);
    // save/load class member variables
    ar & member1;
    ar & member2;
    // if its a recent version of the class
    if(1 < file_version)
        // save load recently added class members
        ar & member3;
}

Splitting serialize into Save/Load

There are times when it is inconvenient to use the same template for both save and load functions. For example, this might occur if versioning gets complex.

Splitting Member Functions

For member functions this can be addressed by including the header file boost/serialization/split_member.hpp including code like this in the class:

template<class Archive>
void save(Archive & ar, const unsigned int version) const
{
    // invoke serialization of the base class 
    ar << boost::serialization::base_object<const base_class_of_T>(*this);
    ar << member1;
    ar << member2;
    ar << member3;
}
template<class Archive>
void load(Archive & ar, const unsigned int version)
{
    // invoke serialization of the base class 
    ar >> boost::serialization::base_object<base_class_of_T>(*this);
    ar >> member1;
    ar >> member2;
    if(version > 0)
        ar >> member3;
}
template<class Archive>
void serialize(
    Archive & ar,
    const unsigned int file_version 
){
    boost::serialization::split_member(ar, *this, file_version);
}
This splits the serialization into two separate functions save and load. Since the new serialize template is always the same it can be generated by invoking the macro BOOST_SERIALIZATION_SPLIT_MEMBER() defined in the header file boost/serialization/split_member.hpp . So the entire serialize function above can be replaced with:

BOOST_SERIALIZATION_SPLIT_MEMBER()

Splitting Free Functions

The situation is same for non-intrusive serialization with the free serialize function template. To use save and load function templates rather than serialize:

namespace boost { namespace serialization {
template<class Archive>
void save(Archive & ar, const my_class & t, unsigned int version)
{
    ...
}
template<class Archive>
void load(Archive & ar, my_class & t, unsigned int version)
{
    ...
}
}}
include the header file
boost/serialization/split_free.hpp . and override the free serialize function template:

namespace boost { namespace serialization {
template<class Archive>
inline void serialize(
    Archive & ar,
    my_class & t,
    const unsigned int file_version
){
    split_free(ar, t, file_version); 
}
}}
To shorten typing, the above template can be replaced with the macro:

BOOST_SERIALIZATION_SPLIT_FREE(my_class)
Note that although the functionality to split the serialize function into save/load has been provided, the usage of the serialize function with the corresponding & operator is preferred. The key to the serialization implementation is that objects are saved and loaded in exactly the same sequence. Using the & operator and serialize function guarantees that this is always the case and will minimize the occurrence of hard to find errors related to synchronization of save and load functions.

Also note that BOOST_SERIALIZATION_SPLIT_FREE must be used outside of any namespace.

Pointers

A pointer to any class instance can be serialized with any of the archive save/load operators.

To properly save and restore an object through a pointer the following situations must be addressed:

  1. If the same object is saved multiple times through different pointers, only one copy of the object need be saved.
  2. If an object is loaded multiple times through different pointers, only one new object should be created and all returned pointers should point to it.
  3. The system must detect the case where an object is first saved through a pointer then the object itself is saved. Without taking extra precautions, loading would result in the creation of multiple copies of the original object. This system detects this case when saving and throws an exception - see below.
  4. An object of a derived class may be stored through a pointer to the base class. The true type of the object must be determined and saved. Upon restoration the correct type must be created and its address correctly cast to the base class. That is, polymorphic pointers have to be considered.
  5. NULL pointers must be dectected when saved and restored to NULL when deserialized.
This serialization library addresses all of the above considerations. The process of saving and loading an object through a pointer is non-trivial. It can be summarized as follows:

Saving a pointer:

  1. determine the true type of the object being pointed to.
  2. write a special tag to the archive
  3. if the object pointed to has not already been written to the archive, do so now
Loading a pointer:
  1. read a tag from the archive.
  2. determine the type of object to be created
  3. if the object has already been loaded, return its address.
  4. otherwise, create a new instance of the object
  5. read the data back in using the operators described above
  6. return the address of the newly created object.
Given that class instances are saved/loaded to/from the archive only once, regardless of how many times they are serialized with the << and >> operators
  • Loading the same pointer object multiple times results in only one object being created, thereby replicating the original pointer configuration.
  • Structures, such as collections of polymorphic pointers, are handled with no special effort on the part of users of this library.
Serialization of pointers of derived types through a pointer to the base class may require a little extra "help". Also, the programmer may desire to modify the process described above for his own reasons. For example, it might be desired to suppress the tracking of objects as it is known a priori that the application in question can never create duplicate objects. Serialization of pointers can be "fine tuned" via the specification of Class Serialization Traits as described in another section of this manual

Non-Default Constructors

Serialization of pointers is implemented in the library with code similar to the following:

// load data required for construction and invoke constructor in place
template<class Archive, class T>
inline void load_construct_data(
    Archive & ar, T * t, const unsigned int file_version
){
    // default just uses the default constructor to initialize
    // previously allocated memory. 
    ::new(t)T();
}
The default load_construct_data invokes the default constructor "in-place" to initialize the memory.

If there is no such default constructor, the function templates load_construct_data and perhaps save_construct_data will have to be overridden. Here is a simple example:


class my_class {
private:
    friend class boost::serialization::access;
    const int m_attribute;  // some immutable aspect of the instance
    int m_state;            // mutable state of this instance
    template<class Archive>
    void serialize(Archive &ar, const unsigned int file_version){
        ar & m_state;
    }
public:
    // no default construct guarentees that no invalid object
    // ever exists
    my_class(int attribute) :
        m_attribute(attribute),
        m_state(0)
    {}
};
the overrides would be:

namespace boost { namespace serialization {
template<class Archive>
inline void save_construct_data(
    Archive & ar, const my_class * t, const unsigned int file_version
){
    // save data required to construct instance
    ar << t->m_attribute;
}
template<class Archive>
inline void load_construct_data(
    Archive & ar, my_class * t, const unsigned int file_version
){
    // retrieve data from archive required to construct new instance
    int attribute;
    ar >> attribute;
    // invoke inplace constructor to initialize instance of my_class
    ::new(t)my_class(attribute);
}
}} // namespace ...
In addition to the deserialization of pointers, these overrides are used in the deserialization of STL containers whose element type has no default constructor.

Pointers to Objects of Derived Classes

Registration

Consider the following:

class base {
    ...
};
class derived_one : public base {
    ...
};
class derived_two : public base {
    ...
};
main(){
    ...
    base *b;
    ...
    ar & b; 
}
When saving b what kind of object should be saved? When loading b what kind of object should be created? Should it be an object of class derived_one, derived_two, or maybe base?

It turns out that the kind of object serialized depends upon whether the base class (base in this case) is polymophic or not. If base is not polymorphic, that is if it has no virtual functions, then an object of the type base will be serialized. Information in any derived classes will be lost. If this is what is desired (it usually isn't) then no other effort is required.

If the base class is polymorphic, an object of the most derived type (derived_one or derived_two in this case) will be serialized. The question of which type of object is to be serialized is (almost) automatically handled by the library.

The system "registers" each class in an archive the first time an object of that class it is serialized and assigns a sequential number to it. Next time an object of that class is serialized in that same archive, this number is written in the archive. So every class is identified uniquely within the archive. When the archive is read back in, each new sequence number is re-associated with the class being read. Note that this implies that "registration" has to occur during both save and load so that the class-integer table built on load is identical to the class-integer table built on save. In fact, the key to whole serialization system is that things are always saved and loaded in the same sequence. This includes "registration".

Expanding our previous example:


main(){
    derived_one d1;
    derived_two d2:
    ...
    ar & d1;
    ar & d2;
    // A side effect of serialization of objects d1 and d2 is that
    // the classes derived_one and derived_two become known to the archive.
    // So subsequent serialization of those classes by base pointer works
    // without any special considerations.
    base *b;
    ...
    ar & b; 
}
When b is read it is preceded by a unique (to the archive) class identifier which has previously been related to class derived_one or derived_two.

If a derived class has NOT been automatically "registered" as described above, an unregistered_class exception will be thrown when serialization is invoked.

This can be addressed by registering the derived class explicitly. All archives are derived from a base class which implements the following template:


template<class T>
register_type();
So our problem could just as well be addressed by writing:

main(){
    ...
    ar.template register_type<derived_one>();
    ar.template register_type<derived_two>();
    base *b;
    ...
    ar & b; 
}
Note that if the serialization function is split between save and load, both functions must include the registration. This is required to keep the save and corresponding load in syncronization.

Export

The above will work but may be inconvenient. We don't always know which derived classes we are going to serialize when we write the code to serialize through a base class pointer. Every time a new derived class is written we have to go back to all the places where the base class is serialized and update the code.

So we have another method:


#include <boost/serialization/export.hpp>
...
BOOST_CLASS_EXPORT_GUID(derived_one, "derived_one")
BOOST_CLASS_EXPORT_GUID(derived_two, "derived_two")
main(){
    ...
    base *b;
    ...
    ar & b; 
}
The macro BOOST_CLASS_EXPORT_GUID associates a string literal with a class. In the above example we've used a string rendering of the class name. If a object of such an "exported" class is serialized through a pointer and is otherwise unregistered, the "export" string is included in the archive. When the archive is later read, the string literal is used to find the class which should be created by the serialization library. This permits each class to be in a separate header file along with its string identifier. There is no need to maintain a separate "pre-registration" of derived classes that might be serialized. This method of registration is referred to as "key export". More information on this topic is found in the section Class Traits - Export Key.

Instantiation

Registration by means of any of the above methods fulfill another role whose importance might not be obvious. This system relies on templated functions of the form template<class Archive, class T>. This means that serialization code must be instantiated for each combination of archive and data type that is serialized in the program.

Polymorphic pointers of derived classes may never be referred to explictly by the program so normally code to serialize such classes would never be instantiated. So in addition to including export key strings in an archive, BOOST_CLASS_EXPORT_GUID explicitly instantiates the class serialization code for all archive classes used by the program.

Selective Tracking

Whether or not an object is tracked is determined by its object tracking trait. The default setting for user defined types is track_selectively. That is, track objects if and only if they are serialized through pointers anywhere in the program. Any objects that are "registered" by any of the above means are presumed to be serialized through pointers somewhere in the program and therefore would be tracked. In certain situations this could lead to an inefficiency. Suppose we have a class module used by multiple programs. Because some programs serializes polymorphic pointers to objects of this class, we export a class identifier by specifying BOOST_CLASS_EXPORT in the class header. When this module is included by another program, objects of this class will always be tracked even though it may not be necessary. This situation could be addressed by using track_never in those programs.

It could also occur that even though a program serializes through a pointer, we are more concerned with efficiency than avoiding the the possibility of creating duplicate objects. It could be that we happen to know that there will be no duplicates. It could also be that the creation of a few duplicates is benign and not worth avoiding given the runtime cost of tracking duplicates. Again, track_never can be used.

Runtime Casting

In order to properly translate between base and derived pointers at runtime, the system requires each base/derived pair be found in a table. A side effect of serializing a base object with boost::serialization::base_object<Base>(Derived &) is to ensure that the base/derived pair is added to the table before the main function is entered. This is very convenient and results in a clean syntax. The only problem is that it can occur where a derived class serialized through a pointer has no need to invoke the serialization of its base class. In such a case, there are two choices. The obvious one is to invoke the base class serialization with base_object and specify an empty function for the base class serialization. The alternative is to "register" the Base/Derived relationship explicitly by invoking the template void_cast_register<Derived, Base>();. Note that this usage of the term "register" is not related to its usage in the previous section. Here is an example of how this is done:

#include <sstream>
#include <boost/serialization/serialization.hpp>
#include <boost/archive/text_iarchive.hpp>
#include <boost/serialization/export.hpp>
class base {
    friend class boost::serialization::access;
    //...
    // only required when using method 1 below
    // no real serialization required - specify a vestigial one
    template<class Archive>
    void serialize(Archive & ar, const unsigned int file_version){}
};
class derived : public base {
    friend class boost::serialization::access;
    template<class Archive>
    void serialize(Archive & ar, const unsigned int file_version){
        // method 1 : invoke base class serialization
        ar & boost::serialization::base_object<base>(*this);
        // method 2 : explicitly register base/derived relationship
        boost::serialization::void_cast_register<derived, base>(
            static_cast<derived *>(NULL),
            static_cast<base *>(NULL)
        )
    }
};
BOOST_CLASS_EXPORT_GUID(derived, "derived")
main(){
    //...
    std::stringstream ss;
    boost::archive::text_iarchive ar(ss);
    base *b;
    ar >> b; 
}

In order for this template to be invoked in code compiled by non-conforming compilers, the following syntax may be used:


boost::serialization::void_cast_register(
    static_cast<Derived *>(NULL),
    static_cast<Base *>(NULL)
);
For more information, see Template Invocation syntax

References

Classes that contain reference members will generally require non-default constructors as references can only be set when an instance is constructed. The example of the previous section is slightly more complex if the class has reference members. This raises the question of how and where the objects being referred to are stored and how are they created. Also there is the question about references to polymorphic base classes. Basically, these are the same questions that arise regarding pointers. This is no surprise as references are really a special kind of pointer. We address these questions by serializing references as though they were pointers.

class object;
class my_class {
private:
    friend class boost::serialization::access;
    int member1;
    object & member2;
    template<class Archive>
    void serialize(Archive &ar, const unsigned int file_version);
public:
    my_class(int m, object & o) :
        member1(m), 
        member2(o)
    {}
};
the overrides would be:

namespace boost { namespace serialization {
template<class Archive>
inline void save_construct_data(
    Archive & ar, const my_class * t, const unsigned int file_version
){
    // save data required to construct instance
    ar << t.member1;
    // serialize reference to object as a pointer
    ar << & t.member2;
}
template<class Archive>
inline void load_construct_data(
    Archive & ar, my_class * t, const unsigned int file_version
){
    // retrieve data from archive required to construct new instance
    int m;
    ar >> m;
    // create and load data through pointer to object
    // tracking handles issues of duplicates.
    object * optr;
    ar >> optr;
    // invoke inplace constructor to initialize instance of my_class
    ::new(t)my_class(m, *optr);
}
}} // namespace ...

Arrays

If T is a serializable type, then any native C++ array of type T is a serializable type. That is, if T is a serializable type, then the following is automatically available and will function as expected:

T t[4];
ar << t;
    ...
ar >> t;

Class Serialization Traits

Serialization Wrappers

Models - Serialization Implementations Included in the Library

The facilities described above are sufficient to implement serialization for all STL containers. In fact, this has been done and has been included in the library. For example, in order to use the included serialization code for std::list, use:

#include <boost/serialization/list.hpp>
rather than

#include <list>
Since the former includes the latter, this is all that is necessary. The same holds true for all STL collections as well as templates required to support them (e.g. std::pair).

As of this writing, the library contains serialization of the following boost classes:

  • optional
  • variant
  • scoped_ptr
  • shared_ptr
  • auto_ptr (demo)
Others are being added to the list so check the boost files section and headers for new implementations!

© Copyright Robert Ramey 2002-2004. Distributed under 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) [ORIG_END] -->

Статья Serialization - Serialization of Classes раздела может быть полезна для разработчиков на c++ и boost.




Материалы статей собраны из открытых источников, владелец сайта не претендует на авторство. Там где авторство установить не удалось, материал подаётся без имени автора. В случае если Вы считаете, что Ваши права нарушены, пожалуйста, свяжитесь с владельцем сайта.



:: Главная :: ::


реклама


©KANSoftWare (разработка программного обеспечения, создание программ, создание интерактивных сайтов), 2007
Top.Mail.Ru

Время компиляции файла: 2024-08-30 11:47:00
2025-05-20 08:46:03/0.014929056167603/0