
Привет, Хабр!
Сегодня рассмотрим библиотеку Cereal в C++, которая позволяет сохранять и загружать состояние объектов, не теряя производительности.
Cereal — это заголовочная библиотека для C++, предназначенная для сериализации данных. Она поддерживает XML и JSON. Помимо этого поддерживает практически все стандартные типы данных в C++ и имеет инструменты для работы с пользовательскими типами. В отличие от, например, библиотек Boost, Cereal не требует сложных настроек и имеет интуитивно понятный синтаксис, знакомый юзерам Boost.
Установим
Cкачаем последнюю версию библиотеки с GitHub:
git clone https://github.com/USCiLab/cereal.git
После скачивания переходим в папку include/cereal в корневом каталоге проекта. Копируем эту папку в директорию, доступную для проекта.
Cereal является заголовочной библиотекой, поэтому дополнительная компиляция не требуется!
Cereal требует компилятора, поддерживающего стандарт C++11. Список поддерживаемых компиляторов:
-
GCC 4.7.3 или новее
-
Clang 3.3 или новее
-
MSVC 2013 или новее
Основной синтаксис
Функции serialize
Функция serialize — основной метод для определения, какие члены класса должны быть сериализованы. Обычно её определяют внутри класса:
struct MyRecord { uint8_t x, y; float z; template <class Archive> void serialize(Archive& ar) { ar(x, y, z); } };
Здесь функция serialize принимает объект архива и передает ему члены класса для сериализации.
Функции save и load
Когда нужно разделить процесс сериализации на загрузку и сохранение, можно юзать функции save и load. Мастхев, когда требуется выполнять доп. действия при загрузке или сохранении данных:
struct SomeData { int32_t id; std::shared_ptr<std::unordered_map<uint32_t, MyRecord>> data; template <class Archive> void save(Archive& ar) const { ar(data); } template <class Archive> void load(Archive& ar) { static int32_t idGen = 0; id = idGen++; ar(data); } };
Функция save должна быть const, т.к она не должна изменять состояние объекта.
Функции save_minimal и load_minimal
Эти функции позволяют минимизировать выходные данные, представляя объект в виде одного примитива или строки. Полезно для упрощения человекочитаемых архивов:
struct MyData { double d; template <class Archive> double save_minimal(Archive const&) const { return d; } template <class Archive> void load_minimal(Archive const&, double const& value) { d = value; } };
Умные указатели
Cereal поддерживает сериализацию умных указателей std::shared_ptr и std::unique_ptr. Так можно ериализовать объекты, на которые ссылаются умные указатели, без лшних усилий:
#include <cereal/types/memory.hpp> struct DataHolder { std::shared_ptr<MyRecord> record; template <class Archive> void serialize(Archive& ar) { ar(record); } };
Наследование
Есть функции cereal::base_class и cereal::virtual_base_class, которые помогают корректно сериализовать базовые и производные классы:
#include <cereal/types/base_class.hpp> struct Base { int x; template <class Archive> void serialize(Archive& ar) { ar(x); } }; struct Derived : public Base { int y; template <class Archive> void serialize(Archive& ar) { ar(cereal::base_class<Base>(this), y); } };
Архивы и их типы
Cereal поддерживает несколько типов архивов: бинарные, XML и JSON архивы. Каждый из них используется для сериализации данных в различных форматах.
Бинарные архивы
#include <cereal/archives/binary.hpp> std::ofstream os("data.cereal", std::ios::binary); cereal::BinaryOutputArchive archive(os); archive(someData);
XML архивы
#include <cereal/archives/xml.hpp> std::ofstream os("data.xml"); cereal::XMLOutputArchive archive(os); archive(someData);
JSON архивы
#include <cereal/archives/json.hpp> std::ofstream os("data.json"); cereal::JSONOutputArchive archive(os); archive(someData);
Версионирование типов
Для управления версиями типов есть макрос CEREAL_CLASS_VERSION, который позволяет задавать версию для каждого типа данных:
#include <cereal/types/base_class.hpp> #include <cereal/types/polymorphic.hpp> struct MyType { int x; template <class Archive> void serialize(Archive& ar, const std::uint32_t version) { ar(x); } }; CEREAL_CLASS_VERSION(MyType, 1);
Примеры использования
Сохранение и загрузка конфигурации приложения
Часто Cereal юзают для сериализация конфигурационных файлов. Рассмотрим пример, где конфигурация приложения хранится в JSON файле, и хотелось бы её сохранять и загружать при запуске приложения:
#include <cereal/archives/json.hpp> #include <cereal/types/map.hpp> #include <cereal/types/string.hpp> #include <fstream> #include <iostream> struct AppConfig { std::map<std::string, std::string> settings; template <class Archive> void serialize(Archive& ar) { ar(settings); } }; void saveConfig(const AppConfig& config, const std::string& filename) { std::ofstream os(filename); cereal::JSONOutputArchive archive(os); archive(config); } AppConfig loadConfig(const std::string& filename) { std::ifstream is(filename); cereal::JSONInputArchive archive(is); AppConfig config; archive(config); return config; } int main() { AppConfig config; config.settings["username"] = "admin"; config.settings["theme"] = "dark"; saveConfig(config, "config.json"); AppConfig loadedConfig = loadConfig("config.json"); std::cout << "Username: " << loadedConfig.settings["username"] << "\n"; std::cout << "Theme: " << loadedConfig.settings["theme"] << "\n"; return 0; }
Сохранение состояния игры
В игрушках было бы хорошо сохранять состояние игры, чтобы игрок мог продолжить с того места, где остановился. Можно легко сохранять и загружать данные игры с помощью Cereal:
#include <cereal/archives/binary.hpp> #include <cereal/types/vector.hpp> #include <fstream> struct GameState { int level; int score; std::vector<int> inventory; template <class Archive> void serialize(Archive& ar) { ar(level, score, inventory); } }; void saveGameState(const GameState& state, const std::string& filename) { std::ofstream os(filename, std::ios::binary); cereal::BinaryOutputArchive archive(os); archive(state); } GameState loadGameState(const std::string& filename) { std::ifstream is(filename, std::ios::binary); cereal::BinaryInputArchive archive(is); GameState state; archive(state); return state; } int main() { GameState state{3, 4500, {1, 2, 3}}; saveGameState(state, "game.sav"); GameState loadedState = loadGameState("game.sav"); std::cout << "Level: " << loadedState.level << "\n"; std::cout << "Score: " << loadedState.score << "\n"; std::cout << "Inventory: "; for (int item : loadedState.inventory) { std::cout << item << " "; } std::cout << "\n"; return 0; }
Сериализация данных для сетевого обмена
В распределенных системах и сетевых приложениях в основном нужно сериализовать данные для передачи по сети.
Пример:
#include <cereal/archives/binary.hpp> #include <cereal/types/string.hpp> #include <cereal/types/vector.hpp> #include <sstream> #include <iostream> struct Message { std::string sender; std::string content; std::vector<int> attachments; template <class Archive> void serialize(Archive& ar) { ar(sender, content, attachments); } }; std::string serializeMessage(const Message& message) { std::ostringstream oss; cereal::BinaryOutputArchive archive(oss); archive(message); return oss.str(); } Message deserializeMessage(const std::string& data) { std::istringstream iss(data); cereal::BinaryInputArchive archive(iss); Message message; archive(message); return message; } int main() { Message msg = {"Alice", "Hello, Bob!", {1, 2, 3}}; std::string serializedData = serializeMessage(msg); Message deserializedMsg = deserializeMessage(serializedData); std::cout << "Sender: " << deserializedMsg.sender << ", Content: " << deserializedMsg.content << std::endl; return 0; }
Как разработчику на С++ организовать кроссплатформенную разработку? Об этом расскажет Арсений Черенков. Встречаемся на бесплатном практическом уроке «Менеджер пакетов Conan для С++проектов» от OTUS.
ссылка на оригинал статьи https://habr.com/ru/articles/821195/
Добавить комментарий