Как сериализовать объект в C++?

У меня есть небольшая иерархия объектов, которые мне нужно сериализовать и передать через соединение сокета. Мне нужно как сериализовать объект, так и десериализовать его в зависимости от типа. Есть ли простой способ сделать это на C++ (как есть в Java)?

существуют ли онлайн-примеры кода сериализации C++ или учебные пособия?

EDIT: чтобы быть ясным, я ищу методы преобразования объекта в массив байтов, а затем обратно в объект. Я смогите отрегулировать передачу гнезда.

3 ответов


говоря о сериализации повысить API сериализации приходит мне на ум. Что касается передачи сериализованных данных по сети, я бы использовал сокеты Berkeley или библиотека asio.

Edit:
Если вы хотите сериализовать свои объекты в массив байтов, вы можете использовать boost serializer следующим образом (взято с сайта учебника):

#include <boost/archive/binary_oarchive.hpp>
#include <boost/archive/binary_iarchive.hpp>
class gps_position
{
private:
    friend class boost::serialization::access;
    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)
    {}
};

фактическая сериализация тогда довольно легко:

#include <fstream>
std::ofstream ofs("filename.dat", std::ios::binary);

    // create class instance
    const gps_position g(35, 59, 24.567f);

    // save data to archive
    {
        boost::archive::binary_oarchive oa(ofs);
        // write class instance to archive
        oa << g;
        // archive and stream closed when destructors are called
    }

десериализация работает аналогичным образом.

есть также механизмы, которые позволяют обрабатывать сериализацию указателей (сложные структуры данных, такие как tress и т. д., не проблема), производные классы, и вы можете выбирать между двоичной и текстовой сериализацией. Кроме того, все контейнеры STL поддерживаются из коробки.


в некоторых случаях, при работе с простыми типами, вы можете сделать:

object o;
socket.write(&o, sizeof(o));

Это нормально, как доказательство концепции или первый проект, так что другие члены вашей команды могут работать над другими деталями.

но рано или поздно, обычно рано, это будет больно!

вы сталкиваетесь с проблемами с:

  • таблицы виртуальных указателей будут повреждены.
  • указатели (на данные / члены / функции) будут испорченный.
  • различия в прокладке / выравнивании на разных машинах.
  • проблемы с порядком байтов Big/Little-Endian.
  • вариации в реализации float / double.

(плюс вам нужно знать, что вы распаковываете на принимающей стороне.)

вы можете улучшить это, разработав свои собственные методы маршалинга/unmarshalling для каждого класса. (Идеально виртуальные, поэтому они могут быть расширены в подклассах.) Несколько простые макросы позволят вам писать очень быстро различные базовые типы в большом/прямой-нейтральной заказа.

но такая ворчливая работа намного лучше и легче обрабатывается через библиотека сериализации boost.


сериализация означает превращение объекта в двоичные данные. В то время как десериализация означает воссоздание объекта из данных.

при сериализации вы нажимаете байты в uint8_t вектор. Когда десериализация Вы читаете байты от uint8_t вектор.

есть, конечно, шаблоны, которые вы можете использовать при сериализации материала.

каждый сериализуемый класс должен иметь serialize(std::vector<uint8_t> &binaryData) или аналогичная signatured функция, которая будет писать свой двоичный файл представительство в векторе. Тогда эта функция может передать этот вектор вниз к сериализации функции-члена, чтобы и они могли оставить свои вещи в него.

поскольку представление данных может быть различным на разных архитектурах. Вам нужно найти схему представления данных.

давайте начнем с основ:

сериализация данных integer

просто записать байт в маленькой прямом порядке. Или использовать варинт представление, если размер имеет значение.

сериализация в маленькой прямом порядке:

data.push_back(integer32 & 0xFF);
data.push_back((integer32 >> 8) & 0xFF);
data.push_back((integer32 >> 16) & 0xFF);
data.push_back((integer32 >> 24) & 0xFF);

десериализация из little endian order:

integer32 = data[0] | (data[1] << 8) | (data[2] << 16) | (data[3] << 24);

сериализация данных с плавающей запятой

насколько я знаю, IEEE 754 имеет монополию здесь. Я не знаю никакой основной архитектуры, которая использовала бы что-то еще для поплавков. Единственное что может отличаться-это порядок байт. Некоторые архитектуры используют little endian, другие - big endian byte порядок. Это означает, что вам нужно быть осторожным, какой заказ вам громче байтов на принимающей стороне. Еще одним различием можно регулировать Донормила и бесконечность и NaN значения. Но пока вы избегаете этих значений, вы должны быть в порядке.

сериализация:

uint8_t mem[8];
memcpy(mem, doubleValue, 8);
data.push_back(mem[0]);
data.push_back(mem[1]);
...

десериализация делает это назад. Следите за порядком байтов вашей архитектуры!

сериализации строки

сначала вам нужно договориться о кодировке. UTF-8 распространен. Затем сохраните его как длину с префиксом: сначала вы сохраняете длину строки с помощью метода, упомянутого выше, а затем записываете строку байт за байтом.

сериализовать массивы.

они такие же, как струны. Сначала сериализуется целое число, представляющее размер массива, а затем сериализуется каждый объект в нем.

сериализовать все объекты

как я уже говорил раньше, они должны иметь serialize метод, который добавляет содержимое в вектор. Чтобы восстановить объект, он должен иметь конструктор, который принимает поток байтов. Это может быть istream но в простейшем случае это может быть просто ссылка