Есть туториал на официальном сайте.
И все бы хорошо, если не множество подводных камней.
Все началось с того, что я пропустил в туториале объявления макросов (T — идентификатор класса):
BOOST_SERIALIZATION_ASSUME_ABSTRACT(T) — для сериализации абстрактных классов (я объявлял его до определения функций сериализации).
BOOST_SERIALIZATION_SPLIT_FREE(T) — если определять сериализацию через save/load — этот макрос для non intrusive версии сериализации, для intrusive (т.е. метод класса) BOOST_SERIALIZATION_SPLIT_MEMBER(). Объявляется после определения функций сериализации. Назначение — генерирует инлайн функцию serialize, которая используется при (де)сериализации для выбора save/load. Запись и чтение в boost::serialization реализованы через один оператор & (амперсанд) который в зависимости от архива, к которому применяется (ахив то шаблонный надо объявлять) интерпретируется как оператор записи или чтения.
BOOST_CLASS_EXPORT(T) — лучше всего писать для всех производных классов, нужен для сериализации через указатель на базовый класс. Иначе получем exception unregistered class. Смотрите объяснение и подробности.
Также можно напоротся на следующие очевидные грабли: например, если сериализовал QPoint как запись x и y, а потом пытаешься десериализовать точку чтением этих x и y из архива, получишь не те результаты, а может и рантайм эррор — так как пропускаешь чтение информации о типе QPoint.
В заголовке я написал в контексте qt. Оказывается, что если попытаться сериализовать какой-либо из стандартных классов qt, получаем ошибку компиляции — can not access private member T::T, что означает — коструктор класса объявлен в секции private. Решение проблемы — перенести макрос Q_DISABLE_COPY в исходниках этого класса из private в public. Смотрите подробности.
Также сильно раздражает абсурдное следование правилам ООП везде где это возможно в qt, а именно: инкапсуляция данных для элементарных классов (практически структур) — QPointF и QRectF. Если бы я мог обратиться к их полям, кода получилось бы раз в 5 меньше. Нет, я должен разбивать функцию serialize на load и save, которые отличаются только тем, что в одной функции
double x = point.x();
ar & x;
а в другой
double x;
ar & x;
point.setX(x);
В примере выше я не могу написать
ar & point.x();
так как почему-то нельзя использовать вычисляемые выражения (ошибка компиляции), заключение в скобки не помогает. Однако можно записать выражение вида
ar & x & y;
NB: нельзя сериализовать qreal, его надо привести к double, причем таким образом
double x = point.x();
ar & x;
а не
ar & double (point.x());
80 (!!!) уровней вложенности
#ifndef SERIALIZATION_H
#define SERIALIZATION_H
#include "polygon.h"
#include <QGraphicsItem>
#include <serialization/base_object.hpp>
#include <boost/serialization/export.hpp>
#include <boost/serialization/split_free.hpp>
BOOST_SERIALIZATION_ASSUME_ABSTRACT(QGraphicsItem)
BOOST_CLASS_EXPORT(PolygonItem)
BOOST_CLASS_EXPORT(QGraphicsLineItem)
BOOST_CLASS_EXPORT(QGraphicsEllipseItem)
BOOST_CLASS_EXPORT(QGraphicsRectItem)
namespace boost {
namespace serialization {
//serialize point
template<class Archive>
void save(Archive &ar, QPointF &point, const unsigned int version)
{
double x = point.x(), y = point.y();
ar & x & y;
}
template<class Archive>
void load(Archive &ar, QPointF &point, const unsigned int version)
{
double x,y;
ar & x;
ar & y;
point.setX(x);
point.setY(y);
}
template<class Archive>
void save(Archive &ar, QRectF &rect, const unsigned int version)
{
double
x = rect.x(),
y = rect.y(),
w = rect.width(),
h = rect.height();
ar & x;
ar & y;
ar & w;
ar & h;
}
template<class Archive>
void load(Archive &ar, QRectF &rect, const unsigned int version)
{
double x,y,w,h;
ar & x;
ar & y;
ar & w;
ar & h;
rect.setX(x);
rect.setY(y);
rect.setWidth(w);
rect.setHeight(h);
}
// save/load for QGraphicsItem
template<class Archive>
void save(Archive & ar, QGraphicsItem & g, const unsigned int version)
{
ar & g.pos();
double x = g.rotation(), y = g.zValue();
ar & x & y;
}
template<class Archive>
void load(Archive & ar, QGraphicsItem & g, const unsigned int version)
{
QPointF point;
ar & point;
double x,y;
ar & x; ar & y;
g.setPos(point);
g.rotate(x);
g.setZValue(y);
}
// save/load ellipse
template<class Archive>
void save(Archive & ar, QGraphicsEllipseItem & g, const unsigned int version)
{
// serialize base class information
ar & boost::serialization::base_object<QGraphicsItem>(g);
ar & g.rect();
}
template<class Archive>
void load(Archive & ar, QGraphicsEllipseItem & g, const unsigned int version)
{
// serialize base class information
ar & boost::serialization::base_object<QGraphicsItem>(g);
QRectF rect;
ar & rect;
g.setRect(rect);
}
// save/load line
template<class Archive>
void save(Archive & ar, QGraphicsLineItem & g, const unsigned int version)
{
// serialize base class information
ar & boost::serialization::base_object<QGraphicsItem>(g);
ar & g.line().p1();
ar & g.line().p2();
}
template<class Archive>
void load(Archive & ar, QGraphicsLineItem & g, const unsigned int version)
{
// serialize base class information
ar & boost::serialization::base_object<QGraphicsItem>(g);
QPointF p1,p2;
ar & p1;
ar & p2;
g.setLine(QLineF (p1,p2));
}
// save/load polygon
template<class Archive>
void save(Archive & ar, PolygonItem & g, const unsigned int version)
{
// serialize base class information
ar & boost::serialization::base_object<QGraphicsItem>(g);
int a = g.points.size();
ar & a;
for (int i = 0; i < g.points.size(); i++)
ar & g.points[i];
}
template<class Archive>
void load(Archive & ar, PolygonItem & g, const unsigned int version)
{
// serialize base class information
ar & boost::serialization::base_object<QGraphicsItem>(g);
int n;
QPointF v;
ar & n;
for (int i = 0; i < n; i++)
{
ar & v;
g.points.push_back(v);
}
}
// save/load rectangle
template<class Archive>
void save(Archive & ar, QGraphicsRectItem & g, const unsigned int version)
{
// serialize base class information
ar & boost::serialization::base_object<QGraphicsItem>(g);
ar & g.rect();
}
template<class Archive>
void load(Archive & ar, QGraphicsRectItem & g, const unsigned int version)
{
// serialize base class information
ar & boost::serialization::base_object<QGraphicsItem>(g);
QRectF rect;
ar & rect;
g.setRect(rect);
}
}
}
BOOST_SERIALIZATION_SPLIT_FREE(QGraphicsEllipseItem)
BOOST_SERIALIZATION_SPLIT_FREE(QGraphicsItem)
BOOST_SERIALIZATION_SPLIT_FREE(QGraphicsRectItem)
BOOST_SERIALIZATION_SPLIT_FREE(QGraphicsLineItem)
BOOST_SERIALIZATION_SPLIT_FREE(PolygonItem)
BOOST_SERIALIZATION_SPLIT_FREE(QPointF)
BOOST_SERIALIZATION_SPLIT_FREE(QRectF)
#endif // SERIALIZATION_H
void MainWindow::on_actionSave_triggered()
{
//create output
std::ofstream ofs ("save.mtf");
boost::archive::text_oarchive oa (ofs);
//save
int size = scene->items().size();
oa & size;
QListIterator<QGraphicsItem *> it (scene->items());
while (it.hasNext())
oa & it.next();
}
void MainWindow::on_actionOpen_triggered()
{
on_actionNew_triggered();
std::ifstream ifs ("save.mtf");
boost::archive::text_iarchive ia (ifs);
int n;
ia & n;
for (int i = 0; i < n; i++)
{
QGraphicsItem * item;
ia & item;
scene->addItem(item);
}
}
В целом, результатом остался доволен. Код получился относительно простой и даже быстрый, вся реализация уместилась в отдельном .h файле. Сериализация 1 миллиона инстансов производного класса QGraphicsLineItem прошла за 4,7 секунды (на моем AMD Athlon X2 QL-65) и сгенерировала файл размером 3.8 мб при сериализации в текстовый поток. Минусы — трудно дебажить, однако спасает множество исключений на рантайм ошибки. Также сам код библиотеки boost::serialization снабжен комментариями типа
//если вы здесь получаете ошибку компиляции, значит serialize уже определен как метод этого класса
В интернетах полно негативных отзывов о boost::serialization, кто-то говорит что она медленная, кто-то недоволен отсутствием контроля версий класса. Однако это отзывы о старых версиях, в версии boost 1.52.0 (которая использовалась в этом обзоре) есть контроль версий класса и производительность на мой взгляд удовлетворительная.
Автор: Petrovich1999