Czy można serializować i deserializować klasę w C ++?


138

Czy można serializować i deserializować klasę w C ++?

Używam Javy od 3 lat i serializacja / deserializacja jest dość trywialna w tym języku. Czy C ++ ma podobne funkcje? Czy istnieją biblioteki natywne, które obsługują serializację?

Przykład byłby pomocny.


2
nie wiesz, co masz na myśli przez „natywny”, czy masz na myśli natywny C ++ (jak Boost.Serialization)? Czy masz na myśli używanie tylko biblioteki standardowej C ++? Masz na myśli coś innego?
jwfearn

1
mam na myśli „nie jest zewnętrzną biblioteką oprogramowania”. Przepraszam, mój angielski nie jest zbyt dobry: S. Jestem z Argentyny
Agusti-N,

3
Nie ma natywnego sposobu serializacji obiektu (nadal możesz zrzucić dane binarne z POD, ale nie dostaniesz tego, czego chcesz). Mimo to Boost, choć nie jest „biblioteką wewnętrzną”, jest pierwszą zewnętrzną biblioteką, którą powinieneś rozważyć dodanie do swojego kompilatora. Boost ma jakość STL (tj. Top Gun C ++)
paercebal

Odpowiedzi:


95

Boost::serializationBiblioteka obsługuje to raczej elegancko. Używałem go w kilku projektach. Oto przykładowy program pokazujący, jak go używać .

Jedynym rodzimym sposobem jest użycie strumieni. To w zasadzie wszystko, co Boost::serializationrobi biblioteka, rozszerza metodę strumieniową, konfigurując strukturę do zapisywania obiektów w formacie tekstowym i odczytywania ich z tego samego formatu.

W przypadku typów wbudowanych lub własnych typów z odpowiednio zdefiniowanymi operator<<i operator>>odpowiednio zdefiniowanymi jest to dość proste; zobacz C ++ FAQ, aby uzyskać więcej informacji.


Wydaje mi się, że funkcja boost :: serialization wymaga od wywołującego śledzenia kolejności, w której obiekty są zapisywane i odczytywane. Czy to jest poprawne? Więc jeśli jest zmiana w kolejności, w jakiej dwa pola są zapisywane między wersjami programu, mamy niezgodność. Czy to jest poprawne?
Agnel Kurian,

1
Prawdopodobnie byłoby to spowodowane funkcjami serializacji, a nie samym kodem Boost :: serialization.
Head Geek,

1
@ 0xDEADBEEF: Prawdopodobnie dzieje się tak podczas używania archiwum binary_ (i | o), które wprowadza inne "problemy", takie jak endian-ness. Wypróbuj archiwum text_ (i | o), jest to bardziej niezależne od platformy.
Ela782

2
Konkretne rozwiązanie ramowe / biblioteczne nie powinno być akceptowaną odpowiedzią.
Andrea

3
@Andrea: Biblioteka Boost to szczególny przypadek. Dopóki C ++ 11 nie został ukończony, pisanie nowoczesnego kodu w C ++ bez niego było prawie niemożliwe, więc był bliżej drugorzędnego STL niż oddzielnej biblioteki.
Head Geek

52

Zdaję sobie sprawę, że to stary post, ale jest to jeden z pierwszych, który pojawia się podczas wyszukiwania c++ serialization .

Zachęcam każdego, kto ma dostęp do C ++ 11, do przyjrzenia się zbożowi , bibliotece C ++ 11 tylko z nagłówkiem do serializacji, która obsługuje pliki binarne, JSON i XML po wyjęciu z pudełka. cereal został zaprojektowany tak, aby był łatwy w rozbudowie i użytkowaniu oraz miał podobną składnię do Boost.


4
Zaletą płatków jest to, że w przeciwieństwie do boostu mają one minimalne metadane (prawie żadnych). boost :: serialization staje się naprawdę irytujący, gdy za każdym razem, gdy otwierasz archiwum, zapisuje jego wersję lib do strumienia, co uniemożliwia dołączanie do pliku.
CyberSnoopy,

@CyberSnoopy - istnieje flaga do wyłączania tej funkcji podczas tworzenia archiwum - oczywiście musisz o tym pamiętać również podczas czytania archiwum.
Robert Ramey

16

Zwiększenie to dobra sugestia. Ale jeśli chcesz toczyć własne, nie jest to takie trudne.

Po prostu potrzebujesz sposobu na zbudowanie wykresu obiektów, a następnie wyprowadzenie ich do jakiegoś strukturalnego formatu przechowywania (JSON, XML, YAML, cokolwiek). Tworzenie wykresu jest tak proste, jak użycie algorytmu oznaczania rekurencyjnego przyzwoitego obiektu, a następnie wyprowadzenie wszystkich zaznaczonych obiektów.

Napisałem artykuł opisujący podstawowy (ale wciąż potężny) system serializacji. Może cię to zainteresować: Używanie SQLite jako formatu pliku na dysku, część 2 .


14

Jeśli chodzi o biblioteki „wbudowane”, <<i>> zostały zarezerwowane specjalnie do serializacji.

Należy przesłonić, <<aby wyprowadzić obiekt do jakiegoś kontekstu serializacji (zwykle iostream) i>> odczytać dane z powrotem z tego kontekstu. Każdy obiekt jest odpowiedzialny za wyprowadzanie swoich zagregowanych obiektów podrzędnych.

Ta metoda działa dobrze, o ile graf obiektu nie zawiera cykli.

Jeśli tak, będziesz musiał skorzystać z biblioteki, aby poradzić sobie z tymi cyklami.


3
Z pewnością nie może być dobrze ... zaimplementowane <<operatory są używane do drukowania reprezentacji obiektów w postaci czytelnej dla człowieka, co bardzo często nie jest tym, czego chcesz do serializacji.
einpoklum

1
@einpoklum Zamiast definiować <<rodzaj ostream, spróbuj zdefiniować go dla strumienia plików.
Carcigenicate

1
@Carcigenicate: plik dziennika, który przyjmuje tekst czytelny dla człowieka, to strumień plików.
einpoklum

1
@einpoklum Nie do końca wiem, co masz na myśli. Frank ma jednak rację, te operatory mogą być używane do serializacji. Właśnie zdefiniowałem je, aby serializować / deserializować wektor.
Carcigenicate

2
Myślę, że haczyk jest tutaj: „Powinieneś przesłonić, <<aby wyprowadzić swój obiekt do jakiegoś kontekstu serializacji… Każdy obiekt jest odpowiedzialny za wyprowadzenie swojego…” - pytanie dotyczy tego, jak uniknąć konieczności pracochłonnego pisania tego dla każdego obiektu: ile może język lub biblioteki pomagają?
ShreevatsaR

14

Polecam bufory protokołów Google . Miałem okazję przetestować bibliotekę w nowym projekcie i jest niezwykle łatwa w użyciu. Biblioteka jest mocno zoptymalizowana pod kątem wydajności.

Protobuf różni się od innych wymienionych tutaj rozwiązań serializacji w tym sensie, że nie serializuje twoich obiektów, ale raczej generuje kod dla obiektów, które są serializowane zgodnie ze specyfikacją.


2
Czy miałeś doświadczenie w serializacji obiektów o rozmiarze około 10-50 MB przy użyciu tego? Dokumentacja wydaje się mówić, że bufory protokołów najlepiej nadają się do obiektów o rozmiarze około MB.
Agnel Kurian,



4

Możesz sprawdzić protokół amef , przykład kodowania C ++ w amef wyglądałby tak,

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

Dekodowanie w javie wyglądałoby następująco:

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

Implementacja protokołu ma kodeki zarówno dla C ++, jak i Java, co ciekawe, może zachować reprezentację klasy obiektów w postaci par nazwa-wartość, potrzebowałem podobnego protokołu w moim ostatnim projekcie, kiedy przypadkowo natknąłem się na ten protokół, faktycznie zmodyfikował bibliotekę bazową zgodnie z moimi wymaganiami. Mam nadzieję, że to ci pomoże.




2

Proponuję zajrzeć do fabryk abstrakcyjnych, które są często używane jako podstawa do serializacji

Odpowiedziałem w innym pytaniu SO dotyczącym fabryk C ++. Proszę, zobacz tam czy interesuje Cię elastyczna fabryka. Próbuję opisać stary sposób z ET ++ do używania makr, który świetnie się sprawdził.

ET ++ był projektem przeniesienia starego MacApp na C ++ i X11. W tym celu Eric Gamma itp. Zaczął myśleć o wzorcach projektowych . ET ++ zawierał automatyczne sposoby serializacji i introspekcji w czasie wykonywania.


0

Jeśli chcesz mieć prostą i najlepszą wydajność i nie zależy Ci na wstecznej kompatybilności danych, wypróbuj HPS , jest lekki, znacznie szybszy niż Boost itp. I znacznie łatwiejszy w użyciu niż Protobuf itp.

Przykład:

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

Oto prosta biblioteka serializatorów, którą podniosłem. To tylko nagłówek, c11 i ma przykłady serializacji podstawowych typów. Oto jeden z mapą do klasy.

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

Wynik:

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

Do implementacji serializacji używam następującego szablonu:

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

Oto Ttyp, który chcesz serializować, Modejest typem fikcyjnym do rozróżniania różnych rodzajów serializacji, np. ta sama liczba całkowita może być serializowana jako little endian, big endian, varint itp.

Domyślnie Serializerdeleguje zadanie do serializowanego obiektu. W przypadku typów wbudowanych należy utworzyć specjalizację szablonu Serializer.

Dostępne są również wygodne szablony funkcji.

Na przykład serializacja little endian liczb całkowitych bez znaku:

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++;
        }
    }
};

Następnie do serializacji:

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

Aby deserializować:

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

Ze względu na abstrakcyjną logikę iteratora powinien działać z dowolnym iteratorem (np. Iteratorami strumienia), wskaźnikiem itp.

Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.