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


138

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

Я використовую Java вже 3 роки, і серіалізація / десеріалізація є досить тривіальною в цій мові. Чи має C ++ подібні функції? Чи є рідні бібліотеки, які обробляють серіалізацію?

Приклад був би корисним.


2
не впевнений, що ви маєте на увазі під "рідним", ви маєте на увазі рідну мову C ++ (наприклад, Boost.Serialization)? Ви маєте на увазі використання лише стандартної бібліотеки C ++? Ви маєте на увазі щось інше?
jwfearn

1
я маю на увазі "не зовнішня бібліотека програмного забезпечення". І вибачте, моя англійська не дуже добре: S. Я з Аргентини
Агусті-Н

3
Не існує нативного способу серіалізації об’єкта (ви все одно можете скидати двійкові дані з POD, але ви не отримаєте те, що хочете). Проте Boost, хоча це не "внутрішня бібліотека", є першою зовнішньою бібліотекою, яку слід розглянути, щоб додати до компілятора. Підвищення якості STL (тобто, Top Gun C ++)
paercebal

Відповіді:


95

Boost::serializationБібліотека обробляє це досить елегантно. Я використовував це в кількох проектах. Ось приклад програми, де показано, як ним користуватися, тут .

Єдиний рідний спосіб зробити це - використовувати потоки. Це по суті всеBoost::serialization робить бібліотека, вона розширює метод потоку, встановлюючи рамки для запису об'єктів у текстовий формат і зчитування їх з того самого формату.

Для вбудованих типів або власних типів із operator<<та operator>>правильно визначеними, це досить просто; див. поширені запитання C ++ для отримання додаткової інформації.


Мені здається, що boost :: серіалізація вимагає від абонента стежити за порядком запису та читання об'єктів. Це правильно? Отже, якщо є зміна порядку, в якому два поля записуються між версіями програми, то ми маємо несумісність. Чи це правильно?
Агнел Куріан

1
Можливо, це пов'язано з функціями серіалізації, а не самим кодом серіалізації Boost ::.
Head Geek

1
@ 0xDEADBEEF: Це, ймовірно, трапляється при використанні архіву binary_ (i | o), який вводить інші "проблеми", такі як ендіанство. Спробуйте text_ (i | o) архів, це більш агресивна платформа.
Ela782

2
Специфічне рішення фреймворку / бібліотеки не повинно бути прийнятою відповіддю.
Андреа

3
@Andrea: Бібліотека Boost - особливий випадок. До тих пір, поки C ++ 11 не було завершено, написати сучасний код C ++ без нього було майже неможливо, тому він був ближчим до вторинної STL, ніж окремої бібліотеки.
Head Geek

52

Я усвідомлюю, що це стара публікація, але це одна з перших, яка з’являється при пошуку c++ serialization .

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


4
Хороша річ у зернових полягає в тому, що вона, на відміну від boost, має мінімальні метадані (майже жодні). boost :: серіалізація стає дійсно дратує, коли кожен раз, коли ви відкриваєте архів, вона записує свою версію 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 відрізняється від інших згаданих тут рішень серіалізації тим, що він не серіалізує ваші об'єкти, а скоріше генерує код для об'єктів, які є серіалізацією відповідно до вашої специфікації.


2
У вас виникла серіалізація об'єктів розміром приблизно 10-50 Мб за допомогою цього? Документація, схоже, говорить про те, що буфери протоколів найкраще підходять для об'єктів розміром з MB.
Агнел Куріан



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. Цікава частина полягає в тому, що вона може зберігати представлення класів об'єктів у вигляді пар значень імен, мені потрібен був аналогічний протокол у моєму останньому проекті, коли я випадково натрапив на цей протокол, я фактично мав змінив базову бібліотеку відповідно до моїх вимог. Сподіваюся, це вам допоможе.




2

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

Я відповів на ще одне питання щодо фабрик С ++. Будь ласка, подивіться там, якщо цікава гнучка фабрика. Я намагаюся описати старий шлях від ET ++, щоб використовувати макроси, які чудово працювали для мене.

ET ++ був проектом для перенесення старого MacApp на C ++ та X11. У зусиллях Ерік Гамма тощо почав думати про дизайнерські шаблони . ET ++ містив автоматичні способи серіалізації та самоаналізу під час виконання.


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);

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- це фіктивний тип для розмежування різних видів серіалізації, наприклад. те саме ціле число можна серіалізувати як маленький ендіан, великий ендіан, варінт тощо.

За замовчуванням 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());

Завдяки абстрактній логіці ітератора він повинен працювати з будь-яким ітератором (наприклад, потоком ітераторів), покажчиком тощо.

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.