Как вы сериализуете объект в C ++? - PullRequest
77 голосов
/ 07 февраля 2009

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

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

РЕДАКТИРОВАТЬ: Просто чтобы прояснить, я ищу методы для преобразования объекта в массив байтов, а затем обратно в объект. Я могу справиться с гнездом передачи.

Ответы [ 3 ]

51 голосов
/ 07 февраля 2009

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

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

13 голосов
/ 07 февраля 2009

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

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

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

Но рано или поздно, обычно раньше , вам будет больно!

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

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

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

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

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

1 голос
/ 07 мая 2018

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

При сериализации вы помещаете байты в вектор uint8_t. При десериализации вы читаете байты из вектора 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 имеет здесь монополию. Я не знаю ни одной основной архитектуры, которая использовала бы что-то еще для поплавков. Единственное, что может отличаться - это порядок байтов. Некоторые архитектуры используют байты с прямым порядком байтов, другие используют порядок байтов с прямым порядком байтов. Это означает, что вам нужно быть осторожным, в каком порядке вы громче байт на принимающей стороне. Другим отличием может быть обработка значений денормали и бесконечности, а также значений 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

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

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

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

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


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

...