Можно ли сериализовать и десериализовать класс в C ++?

138

Можно ли сериализовать и десериализовать класс в C ++?

Я использую Java уже 3 года, и сериализация / десериализация на этом языке довольно тривиальна. Есть ли в C ++ похожие функции? Существуют ли собственные библиотеки, которые обрабатывают сериализацию?

Пример будет полезен.

Agusti-N
источник
2
не уверен, что вы подразумеваете под "нативным", вы имеете в виду нативный C ++ (например, Boost.Serialization)? Вы имеете в виду использование только стандартной библиотеки C ++? Вы имеете в виду что-то еще?
Jwfearn
1
я имею в виду "не внешняя библиотека программного обеспечения". И извините, мой английский не очень хорошо: S. Я из Аргентины
Agusti-N
3
Не существует собственного способа сериализации объекта (вы все равно можете выгружать двоичные данные из POD, но вы не получите того, что хотите). Тем не менее, Boost, хотя и не является «внутренней библиотекой», является первой внешней библиотекой, которую вы должны добавить в свой компилятор. Повышение качества STL (то есть Top Gun C ++)
paercebal

Ответы:

95

Boost::serializationБиблиотека обрабатывает это довольно элегантно. Я использовал это в нескольких проектах. Вот пример программы, показывающей, как ее использовать, здесь .

Единственный родной способ сделать это - использовать потоки. По сути, это все, что Boost::serializationделает библиотека, это расширяет потоковый метод, устанавливая платформу для записи объектов в текстовый формат и чтения их из того же формата.

Для встроенных типов, или ваших собственных типов с operator<<и operator>>правильно определили, что это довольно просто; см. C ++ FAQ для получения дополнительной информации.

Главный Компьютерщик
источник
Мне кажется, что boost :: serialization требует, чтобы вызывающая сторона отслеживала порядок, в котором объекты записываются и читаются. Это правильно? Таким образом, если есть изменение в порядке, в котором два поля записываются между версиями программы, то мы имеем несовместимость. Это правильно?
Агнель Курьян
1
Вероятно, это связано с функциями сериализации, а не с самим кодом Boost :: serialization.
Глава Geek
1
@ 0xDEADBEEF: Это, вероятно, произошло при использовании архива binary_ (i | o), который создает другие «проблемы», такие как endian-ness. Попробуйте архив text_ (i | o), он более независим от платформы.
Ela782
2
Специальное решение для фреймворка / библиотеки не должно быть принятым ответом.
Андреа
3
@Andrea: библиотека Boost - это особый случай. До завершения C ++ 11 практически невозможно было написать современный код C ++ без него, поэтому он был ближе к вторичному STL, чем к отдельной библиотеке.
Глава Geek
52

Я понимаю, что это старый пост, но он один из первых, который появляется при поиске c++ serialization.

Я призываю всех, кто имеет доступ к C ++ 11, взглянуть на cereal , библиотеку C ++ 11 только для заголовков для сериализации, которая поддерживает двоичные файлы, JSON и XML из коробки. cereal был спроектирован так, чтобы его было легко расширять и использовать, и синтаксис аналогичен Boost

Azoth
источник
4
Хорошая особенность зерновых заключается в том, что в отличие от буста, у него минимальные метаданные (почти нет). boost :: serialization становится действительно раздражающим, когда каждый раз, когда вы открываете архив, он записывает свою версию lib в поток, что делает невозможным добавление файла.
CyberSnoopy
@CyberSnoopy - есть флаг для подавления этой функции при создании архива - конечно, вы должны помнить это и при чтении архива.
Роберт Рами
16

Повышение является хорошим предложением. Но если вы хотите сделать свой собственный ролл, это не так сложно.

По сути, вам просто нужен способ построить граф объектов и затем вывести их в некоторый структурированный формат хранения (JSON, XML, YAML и т. Д.). Построить график так же просто, как использовать алгоритм маркировки рекурсивного достойного объекта и затем вывести все отмеченные объекты.

Я написал статью, описывающую элементарную (но все еще мощную) систему сериализации. Вам может быть интересно: Использование SQLite в качестве формата файлов на диске, часть 2 .

Фрэнк Крюгер
источник
14

Что касается «встроенных» библиотек, то <<и >>были зарезервированы специально для сериализации.

Вы должны переопределить, <<чтобы вывести свой объект в некоторый контекст сериализации (обычно это iostream) и >>прочитать данные обратно из этого контекста. Каждый объект отвечает за вывод своих агрегированных дочерних объектов.

Этот метод работает нормально, пока ваш граф объектов не содержит циклов.

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

Фрэнк Крюгер
источник
3
Конечно, это не может быть правдой ... реализованные <<операторы используются для печати понятных человеку текстовых представлений объектов, что очень часто не то, что вы хотите для сериализации.
einpoklum
1
@einpoklum Вместо определения <<универсального ostream, попробуйте определить его для файлового потока.
Carcigenicate
1
@Carcigenicate: файл журнала, который принимает читабельный текст, является потоком файлов.
einpoklum
1
@einpoklum Я не совсем уверен, что ты имеешь в виду. Фрэнк прав, хотя, эти операторы могут быть использованы для сериализации. Я просто определил их для сериализации / десериализации вектора.
Carcigenicate
2
Я думаю, что здесь есть загвоздка: «Вы должны переопределить, <<чтобы вывести свой объект в некоторый контекст сериализации… Каждый объект отвечает за вывод своего…» - вопрос состоит в том, как избежать трудоемкой записи этого для каждого объекта: сколько может язык или библиотеки помогают?
ShreevatsaR
14

Я рекомендую буферы протокола Google . У меня была возможность протестировать библиотеку в новом проекте, и она удивительно проста в использовании. Библиотека сильно оптимизирована для производительности.

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

yoav.aviram
источник
2
Был ли у вас опыт сериализации объектов размером около 10-50 МБ с помощью этого? Документация говорит, что буферы протокола лучше всего подходят для объектов размером около МБ.
Агнель Курьян
Я свернул свою собственную библиотеку, не использует потоки (пока), так что это действительно для мелких вещей: gist.github.com/earonesty/5ba3a93f391ea03ef90884840f068767
Эрик Аронесты
13

Boost :: serialization - отличный вариант, но я столкнулся с новым проектом: Cereal, который я считаю гораздо более элегантным! Я настоятельно рекомендую исследовать это.

оборота M2tM
источник
4

Вы можете проверить протокол amef , пример кодирования C ++ в amef будет выглядеть так:

    //Create a new AMEF object
    AMEFObject *object = new AMEFObject();

    //Add a child string object
    object->addPacket("This is the Automated Message Exchange Format Object property!!","adasd");   

    //Add a child integer object
    object->addPacket(21213);

    //Add a child boolean object
    object->addPacket(true);

    AMEFObject *object2 = new AMEFObject();
    string j = "This is the property of a nested Automated Message Exchange Format Object";
    object2->addPacket(j);
    object2->addPacket(134123);
    object2->addPacket(false);

    //Add a child character object
    object2->addPacket('d');

    //Add a child AMEF Object
    object->addPacket(object2);

    //Encode the AMEF obejct
    string str = new AMEFEncoder()->encode(object,false);

Расшифровка в Java будет как,

    string arr = amef encoded byte array value;
    AMEFDecoder decoder = new AMEFDecoder()
    AMEFObject object1 = AMEFDecoder.decode(arr,true);

Реализация протокола имеет кодеки как для C ++, так и для Java, интересная часть заключается в том, что она может сохранять представление класса объекта в виде пар «имя-значение». Мне потребовался аналогичный протокол в моем последнем проекте, когда я случайно наткнулся на этот протокол, я фактически изменил базовую библиотеку в соответствии с моими требованиями. Надеюсь, это поможет вам.

Дейв
источник
3

Sweet Persist это еще один.

Существует возможность сериализации в и из потоков в XML, JSON, Lua и двоичных форматах.

Винсент
источник
Этот сайт, кажется, не работает, все, что я мог найти, это старый
репозиторий
2

Я предлагаю изучить абстрактные фабрики, которые часто используются в качестве основы для сериализации

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

ET ++ был проектом для переноса старого MacApp на C ++ и X11. В связи с этим Эрик Гамма и другие начали задумываться о дизайне шаблонов . ET ++ содержал автоматические способы сериализации и самоанализа во время выполнения.

epatel
источник
0

Если вам нужна простая и лучшая производительность и вам не нужна обратная совместимость данных, попробуйте HPS , он легкий, намного быстрее, чем Boost и т. Д., И намного проще в использовании, чем Protobuf и т. Д.

Пример:

std::vector<int> data({22, 333, -4444});
std::string serialized = hps::serialize_to_string(data);
auto parsed = hps::parse_from_string<std::vector<int>>(serialized);
streaver91
источник
0

Вот простая библиотека сериализатора, которую я собрал. Это только заголовок, c11 и есть примеры для сериализации основных типов. Вот один для карты в класс.

https://github.com/goblinhack/simple-c-plus-plus-serializer

#include "c_plus_plus_serializer.h"

class Custom {
public:
    int a;
    std::string b;
    std::vector c;

    friend std::ostream& operator<<(std::ostream &out, 
                                    Bits my)
    {
        out << bits(my.t.a) << bits(my.t.b) << bits(my.t.c);
        return (out);
    }

    friend std::istream& operator>>(std::istream &in, 
                                    Bits my)
    {
        in >> bits(my.t.a) >> bits(my.t.b) >> bits(my.t.c);
        return (in);
    }

    friend std::ostream& operator<<(std::ostream &out, 
                                    class Custom &my)
    {
        out << "a:" << my.a << " b:" << my.b;

        out << " c:[" << my.c.size() << " elems]:";
        for (auto v : my.c) {
            out << v << " ";
        }
        out << std::endl;

        return (out);
    }
};

static void save_map_key_string_value_custom (const std::string filename)
{
    std::cout << "save to " << filename << std::endl;
    std::ofstream out(filename, std::ios::binary );

    std::map< std::string, class Custom > m;

    auto c1 = Custom();
    c1.a = 1;
    c1.b = "hello";
    std::initializer_list L1 = {"vec-elem1", "vec-elem2"};
    std::vector l1(L1);
    c1.c = l1;

    auto c2 = Custom();
    c2.a = 2;
    c2.b = "there";
    std::initializer_list L2 = {"vec-elem3", "vec-elem4"};
    std::vector l2(L2);
    c2.c = l2;

    m.insert(std::make_pair(std::string("key1"), c1));
    m.insert(std::make_pair(std::string("key2"), c2));

    out << bits(m);
}

static void load_map_key_string_value_custom (const std::string filename)
{
    std::cout << "read from " << filename << std::endl;
    std::ifstream in(filename);

    std::map< std::string, class Custom > m;

    in >> bits(m);
    std::cout << std::endl;

    std::cout << "m = " << m.size() << " list-elems { " << std::endl;
    for (auto i : m) {
        std::cout << "    [" << i.first << "] = " << i.second;
    }
    std::cout << "}" << std::endl;
}

void map_custom_class_example (void)
{
    std::cout << "map key string, value class" << std::endl;
    std::cout << "============================" << std::endl;
    save_map_key_string_value_custom(std::string("map_of_custom_class.bin"));
    load_map_key_string_value_custom(std::string("map_of_custom_class.bin"));
    std::cout << std::endl;
}

Вывод:

map key string, value class
============================
save to map_of_custom_class.bin
read from map_of_custom_class.bin

m = 2 list-elems {
    [key1] = a:1 b:hello c:[2 elems]:vec-elem1 vec-elem2
    [key2] = a:2 b:there c:[2 elems]:vec-elem3 vec-elem4
}
Нил МакГилл
источник
0

Я использую следующий шаблон для реализации сериализации:

template <class T, class Mode = void> struct Serializer
{
    template <class OutputCharIterator>
    static void serializeImpl(const T &object, OutputCharIterator &&it)
    {
        object.template serializeThis<Mode>(it);
    }

    template <class InputCharIterator>
    static T deserializeImpl(InputCharIterator &&it, InputCharIterator &&end)
    {
        return T::template deserializeFrom<Mode>(it, end);
    }
};

template <class Mode = void, class T, class OutputCharIterator>
void serialize(const T &object, OutputCharIterator &&it)
{
    Serializer<T, Mode>::serializeImpl(object, it);
}

template <class T, class Mode = void, class InputCharIterator>
T deserialize(InputCharIterator &&it, InputCharIterator &&end)
{
    return Serializer<T, Mode>::deserializeImpl(it, end);
}

template <class Mode = void, class T, class InputCharIterator>
void deserialize(T &result, InputCharIterator &&it, InputCharIterator &&end)
{
    result = Serializer<T, Mode>::deserializeImpl(it, end);
}

Вот Tтип, который вы хотите сериализовать Mode, это фиктивный тип, чтобы различать разные виды сериализации, например. одно и то же целое число может быть сериализовано как little-endian, big endian, varint и т. д.

По умолчанию Serializerделегирует задачу сериализуемому объекту. Для встроенных типов вы должны сделать шаблон специализации Serializer.

Удобные шаблоны функций также предоставляются.

Например, немного порядковый номер сериализации без знака целых чисел:

struct LittleEndianMode
{
};

template <class T>
struct Serializer<
    T, std::enable_if_t<std::is_unsigned<T>::value, LittleEndianMode>>
{
    template <class InputCharIterator>
    static T deserializeImpl(InputCharIterator &&it, InputCharIterator &&end)
    {
        T res = 0;

        for (size_t i = 0; i < sizeof(T); i++)
        {
            if (it == end) break;
            res |= static_cast<T>(*it) << (CHAR_BIT * i);
            it++;
        }

        return res;
    }

    template <class OutputCharIterator>
    static void serializeImpl(T number, OutputCharIterator &&it)
    {
        for (size_t i = 0; i < sizeof(T); i++)
        {
            *it = (number >> (CHAR_BIT * i)) & 0xFF;
            it++;
        }
    }
};

Затем сериализовать:

std::vector<char> serialized;
uint32_t val = 42;
serialize<LittleEndianMode>(val, std::back_inserter(serialized));

Для десериализации:

uint32_t val;
deserialize(val, serialized.begin(), serialized.end());

Из-за абстрактной логики итератора он должен работать с любым итератором (например, потоковыми итераторами), указателем и т. Д.

Calmarius
источник