Что такое сериализация и десериализация в C++?

Ответ

Сериализация — это процесс преобразования объекта или структуры данных C++ в последовательность байтов или другой формат, пригодный для хранения (в файл, БД) или передачи (по сети). Десериализация — обратный процесс восстановления объекта из этого формата.

Зачем это нужно в C++ проектах:

  1. Сохранение и загрузка состояния: Конфигурации, игровые сохранения, кэш.
  2. Межпроцессное взаимодействие (IPC) и сетевое программирование: Передача сложных структур данных между приложениями или по сети (например, в gRPC, игровых серверах).
  3. Глубокое копирование объектов: Через сериализацию в память и последующую десериализацию.

Подходы и библиотеки в C++:

1. Ручная сериализация (для простых случаев):

#include <fstream>

struct Config {
    int resolutionX;
    int resolutionY;
    bool fullscreen;

    void save(const std::string& filename) const {
        std::ofstream file(filename, std::ios::binary);
        file.write(reinterpret_cast<const char*>(&resolutionX), sizeof(resolutionX));
        file.write(reinterpret_cast<const char*>(&resolutionY), sizeof(resolutionY));
        file.write(reinterpret_cast<const char*>(&fullscreen), sizeof(fullscreen));
    }

    void load(const std::string& filename) {
        std::ifstream file(filename, std::ios::binary);
        file.read(reinterpret_cast<char*>(&resolutionX), sizeof(resolutionX));
        file.read(reinterpret_cast<char*>(&resolutionY), sizeof(resolutionY));
        file.read(reinterpret_cast<char*>(&fullscreen), sizeof(fullscreen));
    }
};

Недостатки: Не переносима (размер типов, порядок байт), не работает с полиморфизмом, сложна для вложенных структур.

2. Использование библиотек:

  • Текстовые форматы (человекочитаемые):

    • JSON: Библиотеки nlohmann/json, RapidJSON. Идеально для конфигов, веб-API.
      
      #include <nlohmann/json.hpp>
      using json = nlohmann::json;

    struct Data { int id; std::string name; }; void to_json(json& j, const Data& d) { j = json{{"id", d.id}, {"name", d.name}}; } void from_json(const json& j, Data& d) { j.at("id").get_to(d.id); j.at("name").get_to(d.name); }

    Data obj{1, "Test"}; json j = obj; // Сериализация в JSON строку std::string serialized = j.dump(); Data obj2 = j.get(); // Десериализация

  • Бинарные форматы (компактные и быстрые):

    • Protocol Buffers (protobuf), FlatBuffers: Кросс-языковые, с предварительной компиляцией схемы (.proto файл). Очень эффективны для сетевого обмена.
    • Boost.Serialization: Мощная, поддерживает полиморфизм, версионность, но добавляет зависимость от Boost.
    • cereal: Заголовочная библиотека, похожая на Boost.Serialization, но без зависимостей от Boost.
      
      #include <cereal/archives/binary.hpp>
      #include <cereal/types/string.hpp>
      #include <fstream>

    struct MyData { int version; std::string message;

    template <class Archive>
    void serialize(Archive& archive) {
        archive(version, message); // Одна функция для сериализации и десериализации
    }

    };

    // Сериализация MyData data_out{2, "Hello Cereal"}; std::ofstream os("data.bin", std::ios::binary); cereal::BinaryOutputArchive archive_out(os); archive_out(data_out);

    // Десериализация MyData data_in; std::ifstream is("data.bin", std::ios::binary); cereal::BinaryInputArchive archive_in(is); archive_in(data_in);

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

Ответ 18+ 🔞

Давай разберемся, что за магия такая — сериализация в C++. Представь, что у тебя есть объект, целая структура с кучей полей, как будто твой личный бардак в комнате. И тебе надо этот бардак аккуратно сложить в коробку (это сериализация), чтобы отправить другу по почте или просто на балкон закинуть. А потом, когда понадобится, достать коробку и разложить всё обратно, чтобы получился точь-в-точь такой же бардак (это десериализация). Вот и вся суть, ёпта.

Зачем это, спрашивается, нужно? Да овердохуища причин!

  1. Сохраниться, чтобы не начинать сначала. Как в игре — прошёл уровень, сохранился. Или настройки программы записать, чтобы не тыкать их каждый раз.
  2. Переслать данные куда подальше. По сети, между программами, из одного процесса в другой. Без этого ни один мессенджер или онлайн-игра не работали бы.
  3. Сделать глубокую копию объекта. Иногда = не спасает, а вот сериализовать в память и тут же восстановить — самое то.

А как это делается? Есть несколько путей, от самых кривых до цивилизованных.

1. Ручная возня, или «я сам, я сам». Подходит, если структура проще пареной репы и переносимость тебя не ебёт. Пишешь всё в бинарник побайтово.

#include <fstream>

struct Config {
    int resolutionX;
    int resolutionY;
    bool fullscreen;

    void save(const std::string& filename) const {
        std::ofstream file(filename, std::ios::binary);
        file.write(reinterpret_cast<const char*>(&resolutionX), sizeof(resolutionX));
        file.write(reinterpret_cast<const char*>(&resolutionY), sizeof(resolutionY));
        file.write(reinterpret_cast<const char*>(&fullscreen), sizeof(fullscreen));
    }

    void load(const std::string& filename) {
        std::ifstream file(filename, std::ios::binary);
        file.read(reinterpret_cast<char*>(&resolutionX), sizeof(resolutionX));
        file.read(reinterpret_cast<char*>(&resolutionY), sizeof(resolutionY));
        file.read(reinterpret_cast<char*>(&fullscreen), sizeof(fullscreen));
    }
};

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

2. Библиотеки — вот где цивилизация. Здесь уже можно выбрать: хочешь, чтобы данные человек мог прочитать, или чтобы машине было быстро?

  • Текстовые форматы (для людей). Тот же JSON. Библиотеки nlohmann/json или RapidJSON. Идеально для конфигов, которые иногда и заглянуть надо.

    #include <nlohmann/json.hpp>
    using json = nlohmann::json;
    
    struct Data { int id; std::string name; };
    void to_json(json& j, const Data& d) { j = json{{"id", d.id}, {"name", d.name}}; }
    void from_json(const json& j, Data& d) { j.at("id").get_to(d.id); j.at("name").get_to(d.name); }
    
    Data obj{1, "Test"};
    json j = obj; // Упаковали в JSON
    std::string serialized = j.dump(); // Вот она, строка
    Data obj2 = j.get<Data>(); // И распаковали обратно

    Читаемо, удобно, но размер побольше и парсинг медленнее бинарника.

  • Бинарные форматы (для скорости и компактности). Тут ёперный театр выбора.

    • Protocol Buffers (protobuf), FlatBuffers. Это монстры. Ты описываешь структуру в отдельном .proto файле, а компилятор генерирует тебе код на C++ (и на других языках). Хуй с горы по скорости и размеру. Используют везде, где важна эффективность.
    • Boost.Serialization. Мощная, как трактор. Поддержка полиморфизма, разных версий данных. Но тащит за собой весь Boost, что не всем по душе.
    • cereal. Заголовочная библиотека, почти как Boost.Serialization, но без тяжёлых зависимостей. Красиво и просто.
      
      #include <cereal/archives/binary.hpp>
      #include <cereal/types/string.hpp>
      #include <fstream>

    struct MyData { int version; std::string message;

    template <class Archive>
    void serialize(Archive& archive) {
        archive(version, message); // Всё в одной функции! И туда, и обратно.
    }

    };

    // Упаковываем MyData data_out{2, "Hello Cereal"}; std::ofstream os("data.bin", std::ios::binary); cereal::BinaryOutputArchive archive_out(os); archive_out(data_out);

    // Распаковываем MyData data_in; std::ifstream is("data.bin", std::ios::binary); cereal::BinaryInputArchive archive_in(is); archive_in(data_in);

Так что же выбрать, чувак? Э, бошка думай!

  • Нужно быстро и компактно по сети гонять — protobuf или FlatBuffers.
  • Делаешь конфиг или API для веба — JSON.
  • Хочешь мощь и контроль в чистом C++ проекте — Boost.Serialization или cereal.
  • Совсем да похуй, структура из трёх int — можно и ручками, но потом не жалуйся.

Главное — не изобретай велосипед. Всё уже придумано до нас, ядерная вошь.