Ответ
Сериализация — это процесс преобразования объекта или структуры данных C++ в последовательность байтов или другой формат, пригодный для хранения (в файл, БД) или передачи (по сети). Десериализация — обратный процесс восстановления объекта из этого формата.
Зачем это нужно в C++ проектах:
- Сохранение и загрузка состояния: Конфигурации, игровые сохранения, кэш.
- Межпроцессное взаимодействие (IPC) и сетевое программирование: Передача сложных структур данных между приложениями или по сети (например, в gRPC, игровых серверах).
- Глубокое копирование объектов: Через сериализацию в память и последующую десериализацию.
Подходы и библиотеки в 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(); // Десериализация
- JSON: Библиотеки
-
Бинарные форматы (компактные и быстрые):
- 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. Ручная возня, или «я сам, я сам». Подходит, если структура проще пареной репы и переносимость тебя не ебёт. Пишешь всё в бинарник побайтово.
#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);
- Protocol Buffers (protobuf), FlatBuffers. Это монстры. Ты описываешь структуру в отдельном
Так что же выбрать, чувак? Э, бошка думай!
- Нужно быстро и компактно по сети гонять — protobuf или FlatBuffers.
- Делаешь конфиг или API для веба — JSON.
- Хочешь мощь и контроль в чистом C++ проекте — Boost.Serialization или cereal.
- Совсем да похуй, структура из трёх
int— можно и ручками, но потом не жалуйся.
Главное — не изобретай велосипед. Всё уже придумано до нас, ядерная вошь.
Видео-ответы
▶
▶
▶
▶
▶
▶
▶
▶
▶