Вопрос

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

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

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

Это было полезно?

Решение

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

Редактировать:
Если вы хотите сериализовать свои объекты в массив байтов, вы можете использовать сериализатор 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 поддерживаются "из коробки".

Другие советы

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

Вам нужно решить эту проблему с помощью тегов типов и объединений.

Управление версиями

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

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

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

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


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

Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top