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:
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.
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.
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:
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>, оверрайд будет определен как:<
>Обратите внимание, что мы назвали этот оверрайд «ненавязчивым». Это несколько неточно. Не требуется, чтобы класс имел специальные функции, чтобы он был получен из какого-либо общего базового класса или каких-либо других фундаментальных изменений дизайна. Тем не менее, это потребует доступа к членам класса, которые должны быть сохранены и загружены. Если эти члены<private>, их не удастся сериализовать. Поэтому в некоторых случаях даже при использовании этого «ненавязчивого» метода будут необходимы незначительные модификации класса, подлежащие сериализации. На практике это может быть не такой проблемой, поскольку многие библиотеки (E.G. STL) предоставляют достаточно информации, чтобы разрешить реализацию ненавязчивой сериализации без каких-либо изменений в библиотеке.
Для максимальной переносимости, включите любые бесплатные шаблоны функций и определения в пространстве имен<boost::serialization>. Если переносимость не является проблемой, и используемый компилятор поддерживает ADL (Argument Dependent Lookup), бесплатные функции и шаблоны могут быть в любом из следующих пространств имен:
<boost::serialization>
пространство имен класса архива
пространство имен типа, сериализуемого
Обратите внимание, что на первый взгляд это предположение может показаться неправильным для компиляторов, которые реализуют двухфазный поиск. На самом деле библиотека сериализации использовала, возможно, слишком умный метод для поддержки этого правила даже для таких компиляторов. Те, кто заинтересован в дальнейшем изучении этого, найдут больше информации вserialization.hpp
Независимо от того, какой из вышеперечисленных методов используется, в корпусе сериализованной функции должны указываться данные, подлежащие сохранению/загрузке путем последовательного применения архива<operator &>ко всем членам данных класса.<
{
// save/load class member variables
ar & member1;
ar & member2;
}
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_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;
}
Бывают случаи, когда неудобно использовать один и тот же шаблон как для сохранения, так и для загрузки функций. Например, это может произойти, если версия становится сложной.
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_FREE(my_class)
>Обратите внимание, что, хотя функция разделения функции<
serialize>на<save/load>была предоставлена, предпочтительно использование функции<serialize>с соответствующим оператором<&>. Ключом к реализации сериализации является то, что объекты сохраняются и загружаются в точно такой же последовательности. Использование оператора<&>и функции<serialize>гарантирует, что это всегда так и минимизирует возникновение трудно обнаруживаемых ошибок, связанных с синхронизацией функций<save>и<load>.
Также обратите внимание, что<BOOST_SERIALIZATION_SPLIT_FREE>должно использоваться за пределами любого пространства имен.
Указатель на любой экземпляр класса может быть сериализован с любым из операторов сохранения/загрузки архива.
Для правильного сохранения и восстановления объекта с помощью указателя необходимо рассмотреть следующие ситуации:
Если один и тот же объект сохраняется несколько раз через разные указатели, необходимо сохранить только одну копию объекта.
Если объект загружается несколько раз через разные указатели, то должен быть создан только один новый объект и все возвращенные указатели должны указывать на него.
Система должна обнаружить случай, когда объект сначала сохраняется через указатель, а затем сам объект сохраняется. Без принятия дополнительных мер предосторожности загрузка приведет к созданию нескольких копий исходного объекта. Эта система выявляет этот случай при экономии и бросает исключение — см. ниже.
Объект производного класса может храниться через указатель на базовый класс. Истинный тип объекта должен быть определен и сохранен. При восстановлении необходимо создать правильный тип и правильно отлить его адрес в базовый класс. То есть нужно учитывать полиморфные указатели.
Указатели NULL должны быть вычтены при сохранении и восстановлены в NULL при десериализации.
Эта библиотека сериализации учитывает все вышеперечисленные соображения. Процесс сохранения и загрузки объекта через указатель нетривиален. Его можно резюмировать следующим образом:
Сохранение указателя:
определяют истинный тип указываемого объекта.
напишите специальный тег в архив
, если указанный объект еще не был записан в архив, сделайте это сейчас
. Загрузка указателя:
читайте тег из архива.
определить тип создаваемого объекта
, если объект уже загружен, вернуть его адрес.
в противном случае, создать новый экземпляр объекта
считывать данные обратно при использовании операторов, описанных выше
возвращать адрес вновь созданного объекта.
Учитывая, что экземпляры классов сохраняются/загружаются в/из архива только один раз, независимо от того, сколько раз они сериализуются с операторами<<<>и<>>>
Загрузка одного и того же указательного объекта несколько раз приводит к созданию только одного объекта, тем самым копируя исходную конфигурацию указателя.
Структуры, такие как коллекции полиморфных указателей, обрабатываются без особых усилий со стороны пользователей этой библиотеки.
Сериализация указателей производных типов через указатель на базовый класс может потребовать немного дополнительной «помощи». Кроме того, программист может захотеть изменить описанный выше процесс по своим собственным причинам. Например, может быть желательно подавить отслеживание объектов, поскольку априори известно, что рассматриваемое приложение никогда не может создавать дублирующиеся объекты. Сериализация указателей может быть «тонко настроена» с помощью спецификацииКласса Сериализации признаков, как описано вдругом разделе этого руководства
Сериализация указателей осуществляется в библиотеке с кодом, аналогичным следующему:<
// 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;
}
>
Для вызова этого шаблона в коде, составленном несоответствующими компиляторами, может использоваться следующий синтаксис:<
Классы, которые содержат эталонные элементы, обычно требуют конструкторов без по умолчанию, поскольку ссылки могут быть установлены только при построении экземпляра. Пример предыдущего раздела несколько сложнее, если в классе есть референтные члены. Это поднимает вопрос о том, как и где хранятся объекты, о которых идет речь, и как они создаются. Также возникает вопрос о ссылках на полиморфные базовые классы. В основном это те же вопросы, которые возникают относительно указателей. Это не удивительно, так как ссылки являются особым видом указателей. Мы решаем эти вопросы путем сериализации ссылок, как если бы они были указателями.<
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>является сериализуемым типом, то автоматически доступно и будет функционировать следующее:<
Описанные выше средства достаточны для осуществления сериализации всех контейнеров STL. На самом деле это было сделано и включено в библиотеку. Например, чтобы использовать включенный код сериализации для<std::list>, используйте:<
#include <boost/serialization/list.hpp>
>, а не<
#include <list>
>Поскольку первое включает в себя второе, это все, что необходимо. То же самое относится ко всем коллекциям STL, а также к шаблонам, необходимым для их поддержки (например,<std::pair>).
На момент написания этой статьи библиотека содержит сериализацию следующих классов повышения:
факультативный
вариант
scoped_ptr
shared_ptr
auto_ptr (демо)
Другие добавляются в список, поэтому проверьте раздел файлов повышения и заголовки для новых реализаций!
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.
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;
}
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.
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:
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.
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:
If the same object is saved multiple times through different
pointers, only one copy of the object need be saved.
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.
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.
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.
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:
determine the true type of the object being pointed to.
write a special tag to the archive
if the object pointed to has not already been written
to the archive, do so now
Loading a pointer:
read a tag from the archive.
determine the type of object to be created
if the object has already been loaded, return its address.
otherwise, create a new instance of the object
read the data back in using the operators described above
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
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.
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.
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.
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.
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.
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:
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:
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!
Статья Serialization - Serialization of Classes раздела может быть полезна для разработчиков на c++ и boost.
Материалы статей собраны из открытых источников, владелец сайта не претендует на авторство. Там где авторство установить не удалось, материал подаётся без имени автора. В случае если Вы считаете, что Ваши права нарушены, пожалуйста, свяжитесь с владельцем сайта.