FAQ:STL:C++ сериализация данных — различия между версиями
LogRus (обсуждение | вклад) |
LogRus (обсуждение | вклад) |
||
Строка 101: | Строка 101: | ||
Теперь по пунктам как это работает: | Теперь по пунктам как это работает: | ||
− | #создаём внутри нашего класса метод serialize получает ссылку на архив и номер версии(можно по разному сериализовать в зависимости от версии), если метод приватный, то добавляем в друзья class boost::serialization::access. Метод serialize будет вызываться при сериализации и десериализации | + | #создаём внутри нашего класса метод serialize получает ссылку на архив и номер версии(можно по разному сериализовать в зависимости от версии), если метод приватный, то добавляем в друзья class boost::serialization::access. Метод serialize будет вызываться при сериализации и десериализации |
− | #открываем файл и создаём архив (text_oarchive текстовый выходной архив), в нашем случае текстовый - архив это тот самый класс который выполняет основную работу | + | #открываем файл и создаём архив (text_oarchive текстовый выходной архив), в нашем случае текстовый - архив это тот самый класс который выполняет основную работу |
− | #вызываем всеми любимый оператор | + | #вызываем всеми любимый оператор << - этот оператор вызывает метод serialize для классов или же внешние функции(они идут в комплекте) для встроенных типов и стандартных контейнеров |
− | #text_oarchive::operator | + | #text_oarchive::operator<< вызвал наш метод serialize и передал во внутрь себя, тут возникает вопрос: Почему внутри serialize используется оператор &, а не <<? Ответ: потому, что у выходного архива операторы & и << по сути это одно и тоже, у входного операторы & и >> одно и тоже. Т.е ничто не мешает в коде поменять "ia >> newg;" на "ia & newg;" |
#если нужно изменить метод сериализации достаточно поменять тип архива (для XML архива придётся сделать еще некоторою работу в методе serialize) | #если нужно изменить метод сериализации достаточно поменять тип архива (для XML архива придётся сделать еще некоторою работу в методе serialize) | ||
− | на это в общем, то работа по поддержке сериализации закончена. | + | на это в общем, то работа по поддержке сериализации закончена. |
− | При желании, можно разделить методы для сериализации и десериализации. Кстати boost::seralization гарантирует, что порядок байт при сериализации будет изменён, если потребуется, так что можно спокойно передать long с x86 на PowerPC. | + | При желании, можно разделить методы для сериализации и десериализации. Кстати 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: <syntaxhighlight> | ||
+ | 22 serialization::archive 3 0 0 3 0 0 35 59 24.566999 36 60 25.566999 37 61 26.566999 | ||
+ | </syntaxhighlight> | ||
+ | |||
+ | Файл xml_arch.dump: <syntaxhighlight> | ||
+ | <?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> | ||
За универсальность boost::serialization придётся заплатить: | За универсальность boost::serialization придётся заплатить: |
Версия 09:34, 30 сентября 2008
Иногда нужно сохранить состояние класса в файл, передать состояние класса по сети. Подобные задачи обычно решает сериализация.
Говоря о сериализации, я подразумиваю механизмы перевода некоторого класса, структуры или набора переменных в определённый формат (бинарный, текстовый, 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; }
Указан неподдерживаемый язык.
Вы должны указать язык следующим образом: <source lang="html4strict">...</source>
Поддерживаемые языки:
4cs, 6502acme, 6502kickass, 6502tasm, 68000devpac, abap, actionscript, actionscript3, ada, aimms, algol68, apache, applescript, arm, asm, asp, asymptote, autoconf, autohotkey, autoit, avisynth, awk, bascomavr, bash, basic4gl, bf, bibtex, blitzbasic, bnf, boo, c, caddcl, cadlisp, cfdg, cfm, chaiscript, chapel, cil, clojure, cmake, cobol, coffeescript, cpp, csharp, css, cuesheet, d, dart, dcl, dcpu16, dcs, delphi, diff, div, dos, dot, e, ecmascript, eiffel, email, epc, erlang, euphoria, ezt, f1, falcon, fo, fortran, freebasic, freeswitch, fsharp, gambas, gdb, genero, genie, gettext, glsl, gml, gnuplot, go, groovy, gwbasic, haskell, haxe, hicest, hq9plus, html4strict, html5, icon, idl, ini, inno, intercal, io, ispfpanel, j, java, java5, javascript, jcl, jquery, kixtart, klonec, klonecpp, latex, lb, ldif, lisp, llvm, locobasic, logtalk, lolcode, lotusformulas, lotusscript, lscript, lsl2, lua, m68k, magiksf, make, mapbasic, matlab, mirc, mmix, modula2, modula3, mpasm, mxml, mysql, nagios, netrexx, newlisp, nginx, nimrod, nsis, oberon2, objc, objeck, ocaml, octave, oobas, oorexx, oracle11, oracle8, oxygene, oz, parasail, parigp, pascal, pcre, per, perl, perl6, pf, php, pic16, pike, pixelbender, pli, plsql, postgresql, postscript, povray, powerbuilder, powershell, proftpd, progress, prolog, properties, providex, purebasic, pycon, pys60, python, q, qbasic, qml, racket, rails, rbs, rebol, reg, rexx, robots, rpmspec, rsplus, ruby, rust, sas, scala, scheme, scilab, scl, sdlbasic, smalltalk, smarty, spark, sparql, sql, standardml, stonescript, systemverilog, tcl, teraterm, text, thinbasic, tsql, typoscript, unicon, upc, urbi, uscript, vala, vb, vbnet, vbscript, vedit, verilog, vhdl, vim, visualfoxpro, visualprolog, whitespace, whois, winbatch, xbasic, xml, xpp, yaml, z80, zxbasic
22 serialization::archive 3 0 0 3 0 0 35 59 24.566999 36 60 25.566999 37 61 26.566999Файл xml_arch.dump:
Указан неподдерживаемый язык.
Вы должны указать язык следующим образом: <source lang="html4strict">...</source>
Поддерживаемые языки:
4cs, 6502acme, 6502kickass, 6502tasm, 68000devpac, abap, actionscript, actionscript3, ada, aimms, algol68, apache, applescript, arm, asm, asp, asymptote, autoconf, autohotkey, autoit, avisynth, awk, bascomavr, bash, basic4gl, bf, bibtex, blitzbasic, bnf, boo, c, caddcl, cadlisp, cfdg, cfm, chaiscript, chapel, cil, clojure, cmake, cobol, coffeescript, cpp, csharp, css, cuesheet, d, dart, dcl, dcpu16, dcs, delphi, diff, div, dos, dot, e, ecmascript, eiffel, email, epc, erlang, euphoria, ezt, f1, falcon, fo, fortran, freebasic, freeswitch, fsharp, gambas, gdb, genero, genie, gettext, glsl, gml, gnuplot, go, groovy, gwbasic, haskell, haxe, hicest, hq9plus, html4strict, html5, icon, idl, ini, inno, intercal, io, ispfpanel, j, java, java5, javascript, jcl, jquery, kixtart, klonec, klonecpp, latex, lb, ldif, lisp, llvm, locobasic, logtalk, lolcode, lotusformulas, lotusscript, lscript, lsl2, lua, m68k, magiksf, make, mapbasic, matlab, mirc, mmix, modula2, modula3, mpasm, mxml, mysql, nagios, netrexx, newlisp, nginx, nimrod, nsis, oberon2, objc, objeck, ocaml, octave, oobas, oorexx, oracle11, oracle8, oxygene, oz, parasail, parigp, pascal, pcre, per, perl, perl6, pf, php, pic16, pike, pixelbender, pli, plsql, postgresql, postscript, povray, powerbuilder, powershell, proftpd, progress, prolog, properties, providex, purebasic, pycon, pys60, python, q, qbasic, qml, racket, rails, rbs, rebol, reg, rexx, robots, rpmspec, rsplus, ruby, rust, sas, scala, scheme, scilab, scl, sdlbasic, smalltalk, smarty, spark, sparql, sql, standardml, stonescript, systemverilog, tcl, teraterm, text, thinbasic, tsql, typoscript, unicon, upc, urbi, uscript, vala, vb, vbnet, vbscript, vedit, verilog, vhdl, vim, visualfoxpro, visualprolog, whitespace, whois, winbatch, xbasic, xml, xpp, yaml, z80, zxbasic
<?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>
За универсальность 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); } };
Кое-что порезал, что бы не расслаблялись :)