Сериализация
Введение
Часто требуется сохранить некоторые объекты C++ в хранилище данных какого-то типа, вроде пользовательской сессии или кэша и извлечь их обратно.
Это нетривиальная задача для C++ - нерефлексивного языка. Поэтому CppCMS обеспечивает простую библиотеку сериализации, которая помогает нам выполнять такие задачи.
Инструменты Сериализации CppCMS
Подготовка Объектов
Предположим, у нас есть класс person
, содержащий некоторую информацию и список его потомков - классы child
.
Человек:
- struct person {
- std::string name;
- std::string family;
- std::string occupation;
- std::string martial_state;
- double salary;
- physical parameters;
- std::list<child> children;
- std::vector<std::string> friends;
- };
Потомок:
- struct child {
- std::string name;
- physical parameters;
- }
Физические данные о них:
- struct physical {
- int age;
- double height;
- };
Теперь, чтобы сериализовать наш объект, мы можем наследовать его от класса cppcms::serializable
. Добавьте заголовочный файл <cppcms/serialization.h>
и обновите объект так, чтобы:
- struct child {
- ...
- };
- struct person {
- ...
- };
стало
- struct child : public cppcms::serializable {
- ...
- };
- struct person : public cppcms::serializable {
- ...
- };
Примечание: Так как объект "physical" - POD, мы можем обработать его лучшим образом.
Теперь добавим двум классам выше новый метод, описывающий как их сериализовать:
Потомок:
- void serialize(cppcms::archive &a)
- {
- a & name & cppcms::as_pod(parameters);
- }
Метод получает в качестве параметра cppcms::archive
- объект, способный сохранить/восстановить нужные данные и мы отмечаем каждый элемент: name
и parameters
.
Примечание: поскольку мы знаем, что parameters
- POD-объект, мы используем специальный маркер, описывающий как его скопировать.
Далее делаем то же самое для класса person
:
- void serialize(cppcms::archive &a)
- {
- a & name & family & occupation & martial_state
- & salary & cppcms::as_pod(parameters) & children & friends;
- }
Замечание:
Объект archive
знает, как обращаться со стандартными C++-контейнерами типа std::vector
или std::string
. Он знает, как сохранить примитивные типы вроде double
в виде простых двоичных данных.
Потому, например, т.к. children
это std::list<child>
и child
наследуется от cppcms::serializable
, он знает как автоматически справиться с этой ситуацией.
Если Вы используете собственные или нестандартные контейнеры, возможно потребуется специализация класса: cppcms::archive_traits
для Вашего конкретного класса.
Сохранение и Восстановление Объектов
Предположим, что std::string buffer
- наш контейнер данных.
Таким образом, мы можем сохранить данные в буфер с помощью следующих действий:
- person john;
- ... // проинициализировать его данные
- cppcms::archive a;
- john.save(a); // сохранить их в архив
- buffer = a.str(); // взять буфер
Мы можем загрузить их так:
- person john;
- cppcms::archive a;
- a.str(buffer);
- john.load(a);
Это простейший способ сохранения и загрузки данных, но мы рекомендуем использовать чуть более сложное API которое, как увидим позже, даст нам больше возможностей:
Save:
- person john;
- ... // проинициализировать его данные
- cppcms::serialization_traits<person>::save(john,buffer);
Load:
- person john;
- cppcms::serialization_traits<person>::load(buffer,john);
Использование с Кэшем и Сессиями
Теперь давайте посмотрим, как мы можем использовать инструменты сериализации CppCMS с интерфейсами Кэша и Сессий:
Можем легко закэшировать объекты person
следующим образом:
- person john;
- if(!cache().fetch_data("john",john)) {
- init_person(john);
- cache().store_data("john",john);
- }
Это бы позволило, например, снизить число сложных запросов к базе данных для некоторых общих данных, которые не обновлялись.
Можем сохранить объекты с помощью сессий:
- person me;
- if(session().is_set("me")) {
- session().fetch_data("me",me);
- me.salary += 1;
- session().store_data("me",me);
- }
Использование Стороннего API Сериализации
Принцип
CppCMS API - простое, быстрое и мощное, но сильно зависит от платформы - оно не позволяет передать один и тот же объект между хостами с различной архитектурой или порядком следования байт, не поддерживает версионность и прочее.
Поэтому, в некоторых случаях может потребоваться использование с CppCMS сторонних API.
Это довольно просто, все что нужно сделать - предоставить специализацию класса serialization_traits
- вот в чем и была причина, по которой мы рекомендовали использовать класс serialization_traits
, вместо прямого использования cppcms::archive
.
Boost.Serialization
Давайте посмотрим, как использовать, например, Boost.Serialization с CppCMS.
Это наш простейший класс, поддерживающий Boost.Serialization:
- struct person {
- std::string name;
- std::string family;
- template<typename Archive>
- void serialize(Archive &a,unsigned /*version*/)
- {
- a & name & family;
- }
- };
Давайте определим две небольшие вспомогательные функции для сохранения и загрузки объекта в строку:
- template<typename T>
- void load_from_boost(std::string const &src,T &obj)
- {
- std::stringbuf ss(src);
- {
- boost::archive::binary_iarchive oa(ss);
- oa >> obj;
- }
- }
- template<typename T>
- void save_with_boost(T const &obj,std::string &tgt)
- {
- std::stringbuf ss;
- {
- boost::archive::binary_oarchive oa(ss);
- oa << obj;
- }
- tgt=ss.str();
- }
Теперь все, что нам нужно, это специализация нашего serialization_traits
для корректной обработки person
:
- namespace cppcms {
- template<>
- struct serialization_traits<person> {
- static void save(person const &obj,std::string &tgt)
- {
- save_with_boost(obj,tgt);
- }
- static void load(std::string const &src,person &obj)
- {
- load_from_boost(src,obj);
- }
- };
- }
В дальнейшем можно будет свободно сохранять и загружать объект с помощью CppCMS интерфейса сессии или кэша:
- person john;
- if(!cache().fetch_data("john",john)) {
- init_person(john);
- cache().store_data("john",john);
- }