Сериализация boost::serialization (в контексте QT)

в 12:20, , рубрики: c++, qt, Qt Software, Песочница, сериализация, метки: , ,

Есть туториал на официальном сайте.

И все бы хорошо, если не множество подводных камней.

Все началось с того, что я пропустил в туториале объявления макросов (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());

Дебаг сериализации неприятен

image
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

Источник

* - обязательные к заполнению поля


https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js