Как вы сериализуете объект на С++?

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

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

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

Ответ 1

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

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

#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 поддерживаются из коробки.

Ответ 2

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

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

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

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

У вас возникают проблемы с:

  • Таблицы виртуальных указателей будут повреждены.
  • Указатели (для данных/элементов/функций) будут повреждены.
  • Различия в заполнении/выравнивании на разных машинах.
  • Проблемы с порядком байтов большого/младшего разряда.
  • Вариации в реализации float/double.

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

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

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

Ответ 3

Сериализация означает превращение вашего объекта в двоичные данные. Хотя десериализация означает воссоздание объекта из данных.

При сериализации вы uint8_t байты в вектор uint8_t. Когда unserializing вы читаете байты из вектора uint8_t.

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

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

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

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

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

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

Сериализация в порядке малых порядков:

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

Дезериализация из небольшого порядкового числа:

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

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

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

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

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

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

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

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

Сериализация массивов.

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

Сериализация целых объектов

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

Сериализация графиков объектов

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

Теперь вы выяснили, что вам нужно сериализовать этот объект, на который указывает указатель. Проблема указателей в том, что они действительны только в программе, которая их использует. Вы не можете сериализовать указатель, вы должны прекратить использовать их в объектах. Вместо этого создайте пулы объектов. Этот пул объектов в основном представляет собой динамический массив, который содержит "ящики". Эти поля имеют счетчик ссылок. Не нулевой счетчик ссылок указывает на живой объект, ноль указывает на пустой слот. Затем вы создаете умный указатель, похожий на shared_ptr, который не хранит указатель на объект, а индекс в массиве. Вам также необходимо согласовать индекс, который обозначает нулевой указатель, например. -1.

В основном мы здесь заменили указатели на индексы массива. Теперь при сериализации вы можете упорядочить этот индекс массива, как обычно. Вам не нужно беспокоиться о том, где объект будет находиться в памяти в целевой системе. Просто убедитесь, что у них одинаковый пул объектов.

Поэтому нам нужно сериализовать пулы объектов. Но какие? Ну, когда вы сериализуете граф объектов, вы не сериализуете только объект, вы сериализуете целую систему. Это означает, что сериализация системы не должна начинаться с частей системы. Эти объекты не должны беспокоиться о остальной системе, им нужно только сериализовать индексы массива и их. У вас должна быть процедура сериализации системы, которая организует сериализацию системы и проходит через соответствующие пулы объектов и сериализует их все.

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

Сериализация указателей функций

Не храните указатели в объекте. Имейте статический массив, который содержит указатели на эти функции и сохраняет индекс в объекте.

Поскольку обе программы имеют эту таблицу, скомпилированную в них, использование индекса должно работать.

Сериализация полиморфных типов

Поскольку я сказал, что вам следует избегать указателей в сериализуемых типах, и вместо этого вы должны использовать индексы массивов, полиморфизм просто не может работать, потому что для этого требуются указатели.

Вам нужно работать с типом тегов и союзов.

Versioning

Вдобавок ко всему вышесказанное. Возможно, вам понадобятся разные версии программного обеспечения.

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

При загрузке объекта с другой стороны более новые объекты, возможно, могут обрабатывать старые представления, но более старые не могут обрабатывать новые, поэтому они должны вызывать исключение из этого.

Каждый раз, когда что-то меняется, вы должны указывать номер версии.


Поэтому, чтобы обернуть это, сериализация может быть сложной. Но, к счастью, вам не нужно сериализовывать все в вашей программе, чаще всего передаются только сообщения протокола, которые часто являются старыми старыми структурами. Поэтому вам не нужны сложные трюки, о которых я упоминал выше, слишком часто.

Ответ 4

В качестве учебы я написал простой сериализатор С++ 11. Я пробовал различные другие более тяжелые предложения, но хотелось чего-то, что я мог бы на самом деле понять, когда это пошло не так или не удалось скомпилировать с последним g++ (который случилось для меня с хлопьями; действительно хорошая библиотека, но сложная, и я не мог Грок ошибки, которые компилятор выкинул при обновлении.) В любом случае, это только заголовок и обрабатывает POD-типы, контейнеры, карты и т.д. Нет версии, и это будет только загрузить файлы из той же арки, в которой они были сохранены.

https://github.com/goblinhack/simple-c-plus-plus-serializer

Пример использования:

#include "c_plus_plus_serializer.h"

static void serialize (std::ofstream out)
{
    char a = 42;
    unsigned short b = 65535;
    int c = 123456;
    float d = std::numeric_limits<float>::max();
    double e = std::numeric_limits<double>::max();
    std::string f("hello");

    out << bits(a) << bits(b) << bits(c) << bits(d);
    out << bits(e) << bits(f);
}

static void deserialize (std::ifstream in)
{
    char a;
    unsigned short b;
    int c;
    float d;
    double e;
    std::string f;

    in >> bits(a) >> bits(b) >> bits(c) >> bits(d);
    in >> bits(e) >> bits(f);
}