FAQ:STL:C++ сериализация данных — различия между версиями
RXL (обсуждение | вклад) м |
RXL (обсуждение | вклад) |
||
(не показано 19 промежуточных версий 4 участников) | |||
Строка 1: | Строка 1: | ||
− | + | Иногда нужно сохранить состояние класса в файл, передать состояние класса по сети. Подобные задачи обычно решает сериализация. | |
+ | Говоря о сериализации, я подразумеваю механизмы перевода некоторого класса, структуры или набора переменных в определённый формат (бинарный, текстовый, XML, HTML и т.д.), а также сам процесс перевода. Десериализация - процессы и механизмы, обратные сериализации (восстановление состояния из внешнего источника). | ||
− | [[Category:FAQ]] | + | Самый простой способ, к которому чаще всего прибегают молодые программисты, - это простое копирование памяти в файл или еще куда-то. Т.е. берём указатель на класс/структуру/переменную и копируем N байт в файл. Пример: <syntaxhighlight lang="cpp"> |
+ | ....... | ||
+ | MyClass m; | ||
+ | .............. | ||
+ | std::ofstream f("dump.bin", std::binary); | ||
+ | f.write(&m, sizeof(m)); | ||
+ | f.close(); | ||
+ | ........................ | ||
+ | </syntaxhighlight> | ||
+ | |||
+ | Но этот метод сериализации имеет ряд ограничений и недостатков: | ||
+ | |||
+ | *Допустимо использовать только для POD-структур (POD - Plain Old Data) и встроенных типов. Почему, будет понятно из следующих пунктов. | ||
+ | *Если программистом описан конструктор, то компилятор вправе в класс добавить какие-то свои вспомогательные переменные, что превращает класс в не POD-структуру, на самом деле это не так страшно, но формально это так. | ||
+ | *При сохранении указателей членов класса будут скопированы только адреса, хранимые указателями и, естественно, класс с указателями это не POD-тип | ||
+ | *Если в классе объявленные виртуальные функции (или он унаследован от класса содержащего виртуальные функции), это приводит к тому, что класс будет дополнен указателем на таблицу виртуальных функций, и с этим указателем та же проблема, что и со всеми другими. Опять же не POD-тип. | ||
+ | *Если ваш класс содержит внутри себя не POD типы или унаследован от не POD-типа, то ваш класс тоже не под тип, т.е. нет никакой гарантии, что копирование куска памяти позволит постановить состояние класса. | ||
+ | *Различное выравнивание данных внутри класса может сделать невозможным перенос сохранённого класса на другую платформу или даже в программу, скомпилированную с другими параметрами компиляции. | ||
+ | *Различный порядок байт не позволит переносить данные между такими платформами, как: x86 и PowerPC | ||
+ | *И такая сериализация не даёт сохранить в удобочитаемой форме XML, текст или CSV | ||
+ | |||
+ | Есть много ситуаций, когда создание дампа памяти - вполне допустимый метод сериализации, но также есть другое множество ситуаций, когда такой подход недопустим. | ||
+ | |||
+ | Как только программист задумывается о сериализации данных, ему сразу же хочется выполнять сериализацию всего парой строк кода: легко и изящно, например, так: <syntaxhighlight lang="cpp"> | ||
+ | // вывести состояние класса и всех его членов. | ||
+ | std::cout << myClass; | ||
+ | |||
+ | // загрузить состояние класса из XML | ||
+ | myXML >> myClass; | ||
+ | </syntaxhighlight> | ||
+ | |||
+ | И, естественно, самый простой способ быстро добиться результата - это использовать "велосипед", написанный другими. "Велосипед" возьмём хороший, многофункциональный. Он умеет выполнять сериализацию и десериализацию стандартных контейнеров, классов, указателей, ссылок и еще чего-то. Также он умеет сохранять, работать с различными форматами выходных данных: бинарный, текст, XML. Если очень хочется, то он может сохранить не только в поток, но и куда угодно, например, в вектор или в сокет или выкинуть в пропасть. | ||
+ | |||
+ | Полное описание "велосипеда": http://www.boost.org/doc/libs/1_36_0/libs/serialization/doc/index.html | ||
+ | |||
+ | Вот пример использования (взято из описания): | ||
+ | |||
+ | <syntaxhighlight lang="cpp"> | ||
+ | ///////////////////////////////////////////////////////////// | ||
+ | // 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; | ||
+ | } | ||
+ | </syntaxhighlight> | ||
+ | |||
+ | Теперь по пунктам, как это работает: | ||
+ | |||
+ | #Создаём внутри нашего класса метод serialize, получаем ссылку на архив и номер версии(можно по-разному сериализовать в зависимости от версии), если метод приватный, то добавляем в друзья class boost::serialization::access. Метод serialize будет вызываться при сериализации и десериализации. | ||
+ | #Открываем файл и создаём архив (text_oarchive текстовый выходной архив), в нашем случае текстовый, архив - это тот самый класс, который выполняет основную работу. | ||
+ | #Вызываем всеми любимый оператор << - этот оператор вызывает метод serialize для классов или же внешние функции(они идут в комплекте) для встроенных типов и стандартных контейнеров. | ||
+ | #text_oarchive::operator<< вызвал наш метод serialize и передал вовнутрь себя, тут возникает вопрос: почему внутри serialize используется оператор &, а не <<? Ответ: потому что у выходного архива операторы & и << по сути это одно и тоже, у входного операторы & и >> - одно и тоже. Т.е. ничто не мешает в коде поменять "ia >> newg;" на "ia & newg;". | ||
+ | #Если нужно изменить метод сериализации, достаточно поменять тип архива (для XML архива придётся сделать еще некоторою работу в методе serialize). | ||
+ | |||
+ | На этом, в общем-то, работа по поддержке сериализации закончена. | ||
+ | |||
+ | При желании, можно разделить методы для сериализации и десериализации. Кстати, 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> | ||
+ | |||
+ | 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> | ||
+ | |||
+ | Бинарный архив приводить не стану :) не очень красочно, но занимает он 79 байт, из которых 39 - заголовок и 40 - полезная информация. | ||
+ | |||
+ | <br> | ||
+ | |||
+ | За универсальность boost::serialization придётся заплатить: | ||
+ | |||
+ | *Во время компиляции шаблоны могут разворачиваться довольно долго. | ||
+ | *Скорость: стек вызовов для сериализации какой-нибудь не слишком больной структурки, может быть просто ужасающим - вызовов 20-30. | ||
+ | |||
+ | Но если вы не пишете систему массового обслуживания, то это то, что вам нужно, с помощью этой библиотеки можно даже реализовать маршалинг или RPC. | ||
+ | |||
+ | <br> Надеюсь, в общих чертах понятно, как примерно работает сериализация и десериализация, и если понадобится, можно реализовать свою сериализацию. | ||
+ | |||
+ | <br> Вот пример своей реализации архива, который я использую вместо boost:binary_iarchive (была ОЧЕНЬ важна скорость, а универсальность не очень, но для маршалинга хватает), делал так, чтобы можно было использовать один вместо другого без переделки кода: | ||
+ | |||
+ | <syntaxhighlight lang="cpp"> | ||
+ | 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); | ||
+ | } | ||
+ | }; | ||
+ | </syntaxhighlight> | ||
+ | |||
+ | Кое-что порезал, чтобы не расслаблялись :) | ||
+ | |||
+ | [[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; }
Теперь по пунктам, как это работает:
- Создаём внутри нашего класса метод serialize, получаем ссылку на архив и номер версии(можно по-разному сериализовать в зависимости от версии), если метод приватный, то добавляем в друзья class boost::serialization::access. Метод serialize будет вызываться при сериализации и десериализации.
- Открываем файл и создаём архив (text_oarchive текстовый выходной архив), в нашем случае текстовый, архив - это тот самый класс, который выполняет основную работу.
- Вызываем всеми любимый оператор << - этот оператор вызывает метод serialize для классов или же внешние функции(они идут в комплекте) для встроенных типов и стандартных контейнеров.
- text_oarchive::operator<< вызвал наш метод serialize и передал вовнутрь себя, тут возникает вопрос: почему внутри serialize используется оператор &, а не <<? Ответ: потому что у выходного архива операторы & и << по сути это одно и тоже, у входного операторы & и >> - одно и тоже. Т.е. ничто не мешает в коде поменять "ia >> newg;" на "ia & newg;".
- Если нужно изменить метод сериализации, достаточно поменять тип архива (для 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; }
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); } };
Кое-что порезал, чтобы не расслаблялись :)