FAQ:STL:C++ сериализация данных — различия между версиями

Материал из Весельчак У
Перейти к: навигация, поиск
 
(не показано 16 промежуточных версий 4 участников)
Строка 1: Строка 1:
Периодически возникает потребность, сохранить состояние класса в файл или передать по сети или наоборот зачитать/получить. Подобные задачи обычно решает сериализация, можно конечно сделать простое копирование памяти, но тут естественно возникают ряд проблем:
+
Иногда нужно сохранить состояние класса в файл, передать состояние класса по сети. Подобные задачи обычно решает сериализация.
  
* Выравнивание членов касса или структуры, загрузить класс в другой библиотеке, где выравнивание отключено уже не получится
+
Говоря о сериализации, я подразумеваю механизмы перевода некоторого класса, структуры или набора переменных в определённый формат (бинарный, текстовый, XML, HTML и т.д.), а также сам процесс перевода. Десериализация - процессы и механизмы, обратные сериализации (восстановление состояния из внешнего источника).  
* В гетерогенных средах порядок байт может другим: x86 и PowerPC
+
* Нельзя сохранять, что-то кроме POD структур память на которую ссылаются указатели, таким образом не скопируем.
+
* Нельзя сохранить в Humanreadable форме.
+
  
Естественно выполняя сериализацию хочется, что бы она была максимально прозрачной и удобной.
+
Самый простой способ, к которому чаще всего прибегают молодые программисты, - это простое копирование памяти в файл или еще куда-то. Т.е. берём указатель на класс/структуру/переменную и копируем N байт в файл. Пример: <syntaxhighlight lang="cpp">
 +
.......
 +
MyClass m;
 +
..............
 +
std::ofstream f("dump.bin", std::binary);
 +
f.write(&m, sizeof(m));
 +
f.close();
 +
........................
 +
</syntaxhighlight>
  
Например:
+
Но этот метод сериализации имеет ряд ограничений и недостатков:  
<syntaxhighlight lang="cpp">
+
 
// вывести состояние классы и всех его членов.
+
*Допустимо использовать только для POD-структур (POD - Plain Old Data) и встроенных типов. Почему, будет понятно из следующих пунктов.
 +
*Если программистом описан конструктор, то компилятор вправе в класс добавить какие-то свои вспомогательные переменные, что превращает класс в не POD-структуру, на самом деле это не так страшно, но формально это так.
 +
*При сохранении указателей членов класса будут скопированы только адреса, хранимые указателями и, естественно, класс с указателями это не POD-тип
 +
*Если в классе объявленные виртуальные функции (или он унаследован от класса содержащего виртуальные функции), это приводит к тому, что класс будет дополнен указателем на таблицу виртуальных функций, и с этим указателем та же проблема, что и со всеми другими. Опять же не POD-тип.
 +
*Если ваш класс содержит внутри себя не POD типы или унаследован от не POD-типа, то ваш класс тоже не под тип, т.е. нет никакой гарантии, что копирование куска памяти позволит постановить состояние класса.
 +
*Различное выравнивание данных внутри класса может сделать невозможным перенос сохранённого класса на другую платформу или даже в программу, скомпилированную с другими параметрами компиляции.
 +
*Различный порядок байт не позволит переносить данные между такими платформами, как: x86 и PowerPC
 +
*И такая сериализация не даёт сохранить в удобочитаемой форме XML, текст или CSV
 +
 
 +
Есть много ситуаций, когда создание дампа памяти - вполне допустимый метод сериализации, но также есть другое множество ситуаций, когда такой подход недопустим.
 +
 
 +
Как только программист задумывается о сериализации данных, ему сразу же хочется выполнять сериализацию всего парой строк кода: легко и изящно, например, так: <syntaxhighlight lang="cpp">
 +
// вывести состояние класса и всех его членов.
 
std::cout << myClass;
 
std::cout << myClass;
 +
 
// загрузить состояние класса из XML
 
// загрузить состояние класса из XML
 
myXML >> myClass;  
 
myXML >> myClass;  
</syntaxhighlight>
+
</syntaxhighlight>  
 +
 
 +
И, естественно, самый простой способ быстро добиться результата - это использовать "велосипед", написанный другими. "Велосипед" возьмём хороший, многофункциональный. Он умеет выполнять сериализацию и десериализацию стандартных контейнеров, классов, указателей, ссылок и еще чего-то. Также он умеет сохранять, работать с различными форматами выходных данных: бинарный, текст, XML. Если очень хочется, то он может сохранить не только в поток, но и куда угодно, например, в вектор или в сокет или выкинуть в пропасть.
  
Простой способ добиться результата это использовать велосипед написанный другими это не значит, что вам ни придётся ничего делать и получится всё само.
+
Полное описание "велосипеда": http://www.boost.org/doc/libs/1_36_0/libs/serialization/doc/index.html
Велосипед возьмём хороший много функциональный умеет сериализовывать и десериализовывать: контейнеры, классы, указатели, ссылки. Также сохранять в различных форматах: бинарный, текст, XML. Для продвинутых пользователей может сохранять не только в поток, но и куда угодно, например, в вектор или в сокет или выкинуть в пропасть.
+
  
Описание велосипеда: http://www.boost.org/doc/libs/1_36_0/libs/serialization/doc/index.html
+
Вот пример использования (взято из описания):  
  
Вот пример использования (взято из описания):
 
 
<syntaxhighlight lang="cpp">
 
<syntaxhighlight lang="cpp">
 
/////////////////////////////////////////////////////////////
 
/////////////////////////////////////////////////////////////
Строка 79: Строка 97:
 
     return 0;
 
     return 0;
 
}
 
}
</syntaxhighlight>
+
</syntaxhighlight>  
  
Для поддержки в наших классах мы должны в них реализовать метод serialize. Что он делает он в сериализатор(в терминах boost это archive) передаёт все внутренние данные, в свою очередь архив для простых типов делает копирование в памяти (кстати следит при этом за порядком байт, так что можно спокойно переносить на файл на другую машину с другой архитектурой), для сложных зовёт метод serialize, для стандартных контейнеров тоже, есть методы сериализации для контейнеров простых типов происходит копирование элементов, для контейнеров сложных типов происходит вызов serialize для каждого элемента. Для сериализации контейнеров придётся подключить дополнительные заголовки.
+
Теперь по пунктам, как это работает:
  
Можно разделить методы для сериализации и десериализации, есть поддержка версий архивов, есть макросы для подписи элементов в текстовом файле и XML есть примитивы для создания своих форматов (или я повторяюсь) и еще много чего, классная библиотека.
+
#Создаём внутри нашего класса метод serialize, получаем ссылку на архив и номер версии(можно по-разному сериализовать в зависимости от версии), если метод приватный, то добавляем в друзья class boost::serialization::access. Метод serialize будет вызываться при сериализации и десериализации.
 +
#Открываем файл и создаём архив (text_oarchive текстовый выходной архив), в нашем случае текстовый, архив - это тот самый класс, который выполняет основную работу.
 +
#Вызываем всеми любимый оператор &lt;&lt; - этот оператор вызывает метод serialize для классов или же внешние функции(они идут в комплекте) для встроенных типов и стандартных контейнеров.
 +
#text_oarchive::operator&lt;&lt; вызвал наш метод serialize и передал вовнутрь себя, тут возникает вопрос: почему внутри serialize используется оператор &amp;, а не &lt;&lt;? Ответ: потому что у выходного архива операторы &amp; и &lt;&lt; по сути это одно и тоже, у входного операторы &amp; и &gt;&gt; - одно и тоже. Т.е. ничто не мешает в коде поменять "ia &gt;&gt; newg;" на "ia &amp; newg;".
 +
#Если нужно изменить метод сериализации, достаточно поменять тип архива (для XML архива придётся сделать еще некоторою работу в методе serialize).
  
Но за универсальность boost::serialization придётся заплатить:
+
На этом, в общем-то, работа по поддержке сериализации закончена.  
* Время компиляции шаблоны могут разворачиваться довольно долго
+
* Скорость: стек вызовов для сериализации какой-нибуть не слишком больной структурки может быть просто ужасающим вызовов 20-30.
+
  
Но, если вы не пишете систему массового обслуживания, то это то, что вам нужно с помощью этой библиотеки можно даже реализовать маршалинг или RPC, как больше нравится, т.е. я в своей программе создаю класс, но вместо собственно класса получаю gate к этому классу созданному на другой машине в момент когда я его создал на своей и я могу работать с созданным на другой машине классом, как с созданным на своей, при этом спокойно пользуюсь вызовами с передачей параметров по ссылке или указателю и знаю, что всё будет сериализовано - передано - обработано - передано обратно - десериализовано, так, что я не замечу подмены.
+
При желании, можно разделить методы для сериализации и десериализации. Кстати, boost::seralization гарантирует, что порядок байт при сериализации будет изменён, если потребуется, так что можно спокойно передать long с x86 на PowerPC.  
  
 +
Немного поправленный пример использования и результаты работы:
  
Понятно, как примерно работает сериализация и десериализация, и если понадобится можно реализовать свою сериализацию.
+
<syntaxhighlight lang="cpp">
 +
#include "stdafx.h"
 +
#include <iostream>
 +
#include <fstream>
 +
#include <string>
 +
#include <fstream>
 +
#include <vector>
  
 +
// include headers that implement a archive in simple text format
 +
#include <boost/archive/text_oarchive.hpp>
 +
#include <boost/archive/text_iarchive.hpp>
 +
#include <boost/archive/binary_iarchive.hpp>
 +
#include <boost/archive/binary_oarchive.hpp>
 +
#include <boost/archive/xml_iarchive.hpp>
 +
#include <boost/archive/xml_oarchive.hpp>
 +
// включаем, чтобы сериализация работала с векторами
 +
#include <boost/serialization/vector.hpp>
 +
// включаем, чтобы нормально проходила сериализация XML
 +
#include <boost/serialization/nvp.hpp>
  
Можно, например, реализовать у объектов метод ToString который будет звать такой же метод у всех своих потомком, что бы сериализовать объект в строку.
+
class gps_position
 +
{
 +
private:
 +
friend class boost::serialization::access;
  
 +
template<class Archive>
 +
void serialize(Archive & ar, const unsigned int version)
 +
{
 +
// то же, что и make_nvp, только имя параметра выводится в макросе
 +
ar & BOOST_SERIALIZATION_NVP(degrees);
 +
ar & BOOST_SERIALIZATION_NVP(minutes);
 +
ar & BOOST_SERIALIZATION_NVP(seconds);
 +
}
 +
int degrees;
 +
int minutes;
 +
float seconds;
 +
public:
 +
gps_position(){};
 +
gps_position(int d, int m, float s) :
 +
degrees(d), minutes(m), seconds(s)
 +
{}
 +
};
 +
 +
template<typename TIArch, typename TOArch, typename TClass>
 +
void TestArch(const std::string & file, int flags, const TClass & cont)
 +
{
 +
 +
{ // Сериализуем
 +
std::ofstream ofs(file.c_str(), std::ios::out|flags);
 +
TOArch oa(ofs);
 +
// make_nvp создаёт пару имя-значение, которая отразится в XML
 +
// если не используем XML архив, то можно пару не создавать
 +
oa << boost::serialization::make_nvp("Test_Object", cont);
 +
}
 +
 +
TClass newg;
 +
{ // Десериализуем
 +
std::ifstream ifs(file.c_str(), std::ios::in|flags);
 +
TIArch ia(ifs);
 +
ia >> boost::serialization::make_nvp("Test_Object",newg);
 +
}
 +
 +
{ // Еще раз сериализуем, чтобы потом сравнить результаты двух сериализаций
 +
// и убедиться, что десериализациия прошла корректно
 +
std::ofstream ofs((file+".tmp").c_str(), std::ios::out|flags);
 +
TOArch oa(ofs);
 +
oa << boost::serialization::make_nvp("Test_Object", cont);
 +
}
 +
}
 +
 +
int _tmain(int argc, _TCHAR* argv[])
 +
{
 +
std::ofstream ofs("filename");
 +
 +
std::vector<gps_position> v;
 +
v.push_back(gps_position(35, 59, 24.567f));
 +
v.push_back(gps_position(36, 60, 25.567f));
 +
v.push_back(gps_position(37, 61, 26.567f));
 +
 +
using namespace boost::archive;
 +
TestArch<text_iarchive, text_oarchive>("text_arch.dump", 0, v);
 +
TestArch<binary_iarchive, binary_oarchive>("binary_arch.dump", std::ios::binary, v);
 +
TestArch<xml_iarchive, xml_oarchive>("xml_arch.dump", 0, v);
 +
 +
return 0;
 +
}
 +
</syntaxhighlight>
 +
 +
Файл text_arch.dump: <pre>22 serialization::archive 3 0 0 3 0 0 35 59 24.566999 36 60 25.566999 37 61 26.566999
 +
</pre>
 +
 +
Как видите, использование NVP никак не отразилось на внешнем виде архива.
 +
 +
Файл xml_arch.dump: <syntaxhighlight lang="xml">
 +
<?xml version="1.0" encoding="UTF-8" standalone="yes" ?>
 +
<!DOCTYPE boost_serialization>
 +
<boost_serialization signature="serialization::archive" version="3">
 +
<Test_Object class_id="0" tracking_level="0" version="0">
 +
<count>3</count>
 +
<item class_id="1" tracking_level="0" version="0">
 +
<degrees>35</degrees>
 +
<minutes>59</minutes>
 +
<seconds>24.566999</seconds>
 +
</item>
 +
<item>
 +
<degrees>36</degrees>
 +
<minutes>60</minutes>
 +
<seconds>25.566999</seconds>
 +
</item>
 +
<item>
 +
<degrees>37</degrees>
 +
<minutes>61</minutes>
 +
<seconds>26.566999</seconds>
 +
</item>
 +
</Test_Object>
 +
</boost_serialization>
 +
</syntaxhighlight>
 +
 +
Бинарный архив приводить не стану&nbsp;:) не очень красочно, но занимает он 79 байт, из которых 39 - заголовок и 40 - полезная информация.
 +
 +
<br>
 +
 +
За универсальность boost::serialization придётся заплатить:
 +
 +
*Во время компиляции шаблоны могут разворачиваться довольно долго.
 +
*Скорость: стек вызовов для сериализации какой-нибудь не слишком больной структурки, может быть просто ужасающим - вызовов 20-30.
 +
 +
Но если вы не пишете систему массового обслуживания, то это то, что вам нужно, с помощью этой библиотеки можно даже реализовать маршалинг или RPC.
 +
 +
<br> Надеюсь, в общих чертах понятно, как примерно работает сериализация и десериализация, и если понадобится, можно реализовать свою сериализацию.
 +
 +
<br> Вот пример своей реализации архива, который я использую вместо boost:binary_iarchive (была ОЧЕНЬ важна скорость, а универсальность не очень, но для маршалинга хватает), делал так, чтобы можно было использовать один вместо другого без переделки кода:
  
Вот пример своей реализации архива, который я использую вместо бустового (ибо скорость ОЧЕНЬ важна, а универсальность не очень, но для маршалинга хватает), но делал так, что бы можно было использовать один вместо другого без переделки кода:
 
 
<syntaxhighlight lang="cpp">
 
<syntaxhighlight lang="cpp">
class binary_iarchive
+
class binary_iarchive
{
+
{
public:
+
public:
typedef serialization::container container;
+
typedef serialization::container container;
typedef container::iterator iterator;
+
typedef container::iterator iterator;
  
container &cont_;
+
container &cont_;
size_t currentPos_;
+
size_t currentPos_;
typedef boost::mpl::bool_<false> is_saving;
+
typedef boost::mpl::bool_<false> is_saving;
binary_iarchive(container & cont, long = 0)
+
binary_iarchive(container & cont, long = 0)
: cont_(cont)
+
: cont_(cont)
, currentPos_(0)
+
, currentPos_(0)
{
+
{
}
+
}
  
template<typename T>
+
template<typename T>
binary_iarchive & operator>>(T & val)
+
binary_iarchive & operator>>(T & val)
{
+
{
deserialize_impl(val);
+
deserialize_impl(val);
return *this;
+
return *this;
}
+
}
  
void reset()
+
void reset()
{
+
{
resetPos();
+
resetPos();
cont_.clear();
+
cont_.clear();
}
+
}
  
template<typename T>
+
template<typename T>
inline void raw_read(T beginPos, size_t len)
+
inline void raw_read(T beginPos, size_t len)
{
+
{
if (static_cast<size_t>(cont_.size() - currentPos_) < len)
+
if (static_cast<size_t>(cont_.size() - currentPos_) < len)
throw std::runtime_error("No more data");
+
throw std::runtime_error("No more data");
  
iterator pos = cont_.begin() + currentPos_;
+
iterator pos = cont_.begin() + currentPos_;
iterator endPos = pos + len;
+
iterator endPos = pos + len;
std::copy(pos, endPos, beginPos);
+
std::copy(pos, endPos, beginPos);
currentPos_ = currentPos_ + len;
+
currentPos_ = currentPos_ + len;
}
+
}
private:
+
private:
// Fundamental
+
// Fundamental
template<typename T>
+
template<typename T>
inline void deserialize_impl(T & val, typename boost::enable_if<boost::is_fundamental<T> >::type* dummy = 0)
+
inline void deserialize_impl(T & val, typename boost::enable_if<boost::is_fundamental<T> >::type* dummy = 0)
{
+
{
raw_read(reinterpret_cast<char*>(&val), sizeof(T));
+
raw_read(reinterpret_cast<char*>(&val), sizeof(T));
}
+
}
  
//Classes
+
//Classes
template<typename T>
+
template<typename T>
inline void deserialize_impl(T & val, typename boost::enable_if<boost::is_class<T> >::type* dummy = 0)
+
inline void deserialize_impl(T & val, typename boost::enable_if<boost::is_class<T> >::type* dummy = 0)
{
+
{
deserialize_class(*this, val);
+
deserialize_class(*this, val);
}
+
}
};
+
};
</syntaxhighlight>
+
</syntaxhighlight>  
  
 +
Кое-что порезал, чтобы не расслаблялись&nbsp;:)
  
[[Category:FAQ]]
+
[[Category:FAQ]] [[Category:FAQ:ANSI_CPP]] [[Category:FAQ:STL]] [[Category:FAQ:STL_BOOST]]

Текущая версия на 01:41, 4 августа 2015

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

Говоря о сериализации, я подразумеваю механизмы перевода некоторого класса, структуры или набора переменных в определённый формат (бинарный, текстовый, XML, HTML и т.д.), а также сам процесс перевода. Десериализация - процессы и механизмы, обратные сериализации (восстановление состояния из внешнего источника).

Самый простой способ, к которому чаще всего прибегают молодые программисты, - это простое копирование памяти в файл или еще куда-то. Т.е. берём указатель на класс/структуру/переменную и копируем N байт в файл. Пример:
.......
MyClass m;
..............
std::ofstream f("dump.bin", std::binary);
f.write(&m, sizeof(m));
f.close();
........................

Но этот метод сериализации имеет ряд ограничений и недостатков:

  • Допустимо использовать только для POD-структур (POD - Plain Old Data) и встроенных типов. Почему, будет понятно из следующих пунктов.
  • Если программистом описан конструктор, то компилятор вправе в класс добавить какие-то свои вспомогательные переменные, что превращает класс в не POD-структуру, на самом деле это не так страшно, но формально это так.
  • При сохранении указателей членов класса будут скопированы только адреса, хранимые указателями и, естественно, класс с указателями это не POD-тип
  • Если в классе объявленные виртуальные функции (или он унаследован от класса содержащего виртуальные функции), это приводит к тому, что класс будет дополнен указателем на таблицу виртуальных функций, и с этим указателем та же проблема, что и со всеми другими. Опять же не POD-тип.
  • Если ваш класс содержит внутри себя не POD типы или унаследован от не POD-типа, то ваш класс тоже не под тип, т.е. нет никакой гарантии, что копирование куска памяти позволит постановить состояние класса.
  • Различное выравнивание данных внутри класса может сделать невозможным перенос сохранённого класса на другую платформу или даже в программу, скомпилированную с другими параметрами компиляции.
  • Различный порядок байт не позволит переносить данные между такими платформами, как: x86 и PowerPC
  • И такая сериализация не даёт сохранить в удобочитаемой форме XML, текст или CSV

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

Как только программист задумывается о сериализации данных, ему сразу же хочется выполнять сериализацию всего парой строк кода: легко и изящно, например, так:
// вывести состояние класса и всех его членов.
std::cout << myClass;
 
// загрузить состояние класса из XML
myXML >> myClass;

И, естественно, самый простой способ быстро добиться результата - это использовать "велосипед", написанный другими. "Велосипед" возьмём хороший, многофункциональный. Он умеет выполнять сериализацию и десериализацию стандартных контейнеров, классов, указателей, ссылок и еще чего-то. Также он умеет сохранять, работать с различными форматами выходных данных: бинарный, текст, XML. Если очень хочется, то он может сохранить не только в поток, но и куда угодно, например, в вектор или в сокет или выкинуть в пропасть.

Полное описание "велосипеда": http://www.boost.org/doc/libs/1_36_0/libs/serialization/doc/index.html

Вот пример использования (взято из описания):

/////////////////////////////////////////////////////////////
// gps координаты
//
// illustrates serialization for a simple type
//
class gps_position
{
private:
    friend class boost::serialization::access;
    // When the class Archive corresponds to an output archive, the
    // & operator is defined similar to <<.  Likewise, when the class Archive
    // is a type of input archive the & operator is defined similar to >>.
    template<class Archive>
    void serialize(Archive & ar, const unsigned int version)
    {
        ar & degrees;
        ar & minutes;
        ar & seconds;
    }
    int degrees;
    int minutes;
    float seconds;
public:
    gps_position(){};
    gps_position(int d, int m, float s) :
        degrees(d), minutes(m), seconds(s)
    {}
};
 
int main() {
    // create and open a character archive for output
    std::ofstream ofs("filename");
 
    // create class instance
    const gps_position g(35, 59, 24.567f);
 
    // save data to archive
    {
        boost::archive::text_oarchive oa(ofs);
        // write class instance to archive
        oa << g;
    	// archive and stream closed when destructors are called
    }
 
    // ... some time later restore the class instance to its orginal state
    gps_position newg;
    {
        // create and open an archive for input
        std::ifstream ifs("filename");
        boost::archive::text_iarchive ia(ifs);
        // read class state from archive
        ia >> newg;
        // archive and stream closed when destructors are called
    }
    return 0;
}

Теперь по пунктам, как это работает:

  1. Создаём внутри нашего класса метод serialize, получаем ссылку на архив и номер версии(можно по-разному сериализовать в зависимости от версии), если метод приватный, то добавляем в друзья class boost::serialization::access. Метод serialize будет вызываться при сериализации и десериализации.
  2. Открываем файл и создаём архив (text_oarchive текстовый выходной архив), в нашем случае текстовый, архив - это тот самый класс, который выполняет основную работу.
  3. Вызываем всеми любимый оператор << - этот оператор вызывает метод serialize для классов или же внешние функции(они идут в комплекте) для встроенных типов и стандартных контейнеров.
  4. text_oarchive::operator<< вызвал наш метод serialize и передал вовнутрь себя, тут возникает вопрос: почему внутри serialize используется оператор &, а не <<? Ответ: потому что у выходного архива операторы & и << по сути это одно и тоже, у входного операторы & и >> - одно и тоже. Т.е. ничто не мешает в коде поменять "ia >> newg;" на "ia & newg;".
  5. Если нужно изменить метод сериализации, достаточно поменять тип архива (для XML архива придётся сделать еще некоторою работу в методе serialize).

На этом, в общем-то, работа по поддержке сериализации закончена.

При желании, можно разделить методы для сериализации и десериализации. Кстати, boost::seralization гарантирует, что порядок байт при сериализации будет изменён, если потребуется, так что можно спокойно передать long с x86 на PowerPC.

Немного поправленный пример использования и результаты работы:

#include "stdafx.h"
#include <iostream>
#include <fstream>
#include <string>
#include <fstream>
#include <vector>
 
// include headers that implement a archive in simple text format
#include <boost/archive/text_oarchive.hpp>
#include <boost/archive/text_iarchive.hpp>
#include <boost/archive/binary_iarchive.hpp>
#include <boost/archive/binary_oarchive.hpp>
#include <boost/archive/xml_iarchive.hpp>
#include <boost/archive/xml_oarchive.hpp>
// включаем, чтобы сериализация работала с векторами
#include <boost/serialization/vector.hpp>
// включаем, чтобы нормально проходила сериализация XML
#include <boost/serialization/nvp.hpp>
 
class gps_position
{
private:
	friend class boost::serialization::access;
 
	template<class Archive>
	void serialize(Archive & ar, const unsigned int version)
	{
		// то же, что и make_nvp, только имя параметра выводится в макросе
		ar & BOOST_SERIALIZATION_NVP(degrees);
		ar & BOOST_SERIALIZATION_NVP(minutes);
		ar & BOOST_SERIALIZATION_NVP(seconds);
	}
	int degrees;
	int minutes;
	float seconds;
public:
	gps_position(){};
	gps_position(int d, int m, float s) :
	degrees(d), minutes(m), seconds(s)
	{}
};
 
template<typename TIArch, typename TOArch, typename TClass>
void TestArch(const std::string & file, int flags, const TClass & cont)
{
 
	{ // Сериализуем
		std::ofstream ofs(file.c_str(), std::ios::out|flags);
		TOArch oa(ofs);
		// make_nvp создаёт пару имя-значение, которая отразится в XML
		// если не используем XML архив, то можно пару не создавать
		oa << boost::serialization::make_nvp("Test_Object", cont); 
	}
 
	TClass newg;
	{ // Десериализуем
		std::ifstream ifs(file.c_str(), std::ios::in|flags);
		TIArch ia(ifs);
		ia >> boost::serialization::make_nvp("Test_Object",newg);
	}
 
	{ // Еще раз сериализуем, чтобы потом сравнить результаты двух сериализаций
		// и убедиться, что десериализациия прошла корректно
		std::ofstream ofs((file+".tmp").c_str(), std::ios::out|flags);
		TOArch oa(ofs);
		oa << boost::serialization::make_nvp("Test_Object", cont);
	}
}
 
int _tmain(int argc, _TCHAR* argv[])
{
	std::ofstream ofs("filename");
 
	std::vector<gps_position> v;
	v.push_back(gps_position(35, 59, 24.567f));
	v.push_back(gps_position(36, 60, 25.567f));
	v.push_back(gps_position(37, 61, 26.567f));
 
	using namespace boost::archive;
	TestArch<text_iarchive, text_oarchive>("text_arch.dump", 0, v);
	TestArch<binary_iarchive, binary_oarchive>("binary_arch.dump", std::ios::binary, v);
	TestArch<xml_iarchive, xml_oarchive>("xml_arch.dump", 0, v);
 
	return 0;
}
Файл text_arch.dump:
22 serialization::archive 3 0 0 3 0 0 35 59 24.566999 36 60 25.566999 37 61 26.566999

Как видите, использование NVP никак не отразилось на внешнем виде архива.

Файл xml_arch.dump:
<?xml version="1.0" encoding="UTF-8" standalone="yes" ?>
<!DOCTYPE boost_serialization>
<boost_serialization signature="serialization::archive" version="3">
<Test_Object class_id="0" tracking_level="0" version="0">
	<count>3</count>
	<item class_id="1" tracking_level="0" version="0">
		<degrees>35</degrees>
		<minutes>59</minutes>
		<seconds>24.566999</seconds>
	</item>
	<item>
		<degrees>36</degrees>
		<minutes>60</minutes>
		<seconds>25.566999</seconds>
	</item>
	<item>
		<degrees>37</degrees>
		<minutes>61</minutes>
		<seconds>26.566999</seconds>
	</item>
</Test_Object>
</boost_serialization>

Бинарный архив приводить не стану :) не очень красочно, но занимает он 79 байт, из которых 39 - заголовок и 40 - полезная информация.


За универсальность boost::serialization придётся заплатить:

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

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


Надеюсь, в общих чертах понятно, как примерно работает сериализация и десериализация, и если понадобится, можно реализовать свою сериализацию.


Вот пример своей реализации архива, который я использую вместо boost:binary_iarchive (была ОЧЕНЬ важна скорость, а универсальность не очень, но для маршалинга хватает), делал так, чтобы можно было использовать один вместо другого без переделки кода:

class binary_iarchive
{
public:
	typedef serialization::container container;
	typedef container::iterator iterator;
 
	container &cont_;
	size_t currentPos_;
	typedef boost::mpl::bool_<false> is_saving;
	binary_iarchive(container & cont, long = 0)
			: cont_(cont)
			, currentPos_(0)
	{
	}
 
	template<typename T>
	binary_iarchive & operator>>(T & val)
	{
		deserialize_impl(val);
		return *this;
	}
 
	void reset()
	{
		resetPos();
		cont_.clear();
	}
 
	template<typename T>
	inline void raw_read(T beginPos, size_t len)
	{
		if (static_cast<size_t>(cont_.size() - currentPos_) < len)
			throw std::runtime_error("No more data");
 
		iterator pos = cont_.begin() + currentPos_;
		iterator endPos = pos + len;
		std::copy(pos, endPos, beginPos);
		currentPos_ = currentPos_ + len;
	}
private:
	// Fundamental
	template<typename T>
	inline void deserialize_impl(T & val, typename boost::enable_if<boost::is_fundamental<T> >::type* dummy = 0)
	{
		raw_read(reinterpret_cast<char*>(&val), sizeof(T));
	}
 
	//Classes
	template<typename T>
	inline void deserialize_impl(T & val, typename boost::enable_if<boost::is_class<T> >::type* dummy = 0)
	{
		deserialize_class(*this, val);
	}
};

Кое-что порезал, чтобы не расслаблялись :)