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

«Что такое сериализация и десериализация в C++?» — вопрос из категории C++ Core, который задают на 25% собеседований C/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);

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