Jak serializować obiekt w C++?

Mam małą hierarchię obiektów, które muszę serializować i transmitować przez gniazdo. Muszę zarówno serializować obiekt, a następnie deserializować go na podstawie tego, jaki to typ. Czy jest jakiś łatwy sposób, aby to zrobić w C++ (jak jest w Javie)?

Czy są jakieś próbki kodu serializacji C++ online lub samouczki?

EDIT: dla jasności, Szukam metod konwersji obiektu do tablicy bajtów, a następnie z powrotem do obiektu. Poradzę sobie z Gniazdo transmisji.

Author: Tony The Lion, 2009-02-07

3 answers

Mówiąc o serializacji, przychodzi mi do głowy boost serialization API. Jeśli chodzi o przesyłanie serializowanych danych przez sieć, użyłbym albo gniazd Berkeley albo biblioteki asio .

Edit:
Jeśli chcesz serializować obiekty do tablicy bajtów, możesz użyć serializera Boost w następujący sposób (pobrany ze strony samouczka):

#include <boost/archive/binary_oarchive.hpp>
#include <boost/archive/binary_iarchive.hpp>
class gps_position
{
private:
    friend class boost::serialization::access;
    template<class Archive>
    void serialize(Archive & ar, const unsigned int version)
    {
        ar & degrees;
        ar & minutes;
        ar & seconds;
    }
    int degrees;
    int minutes;
    float seconds;

public:
    gps_position(){};
    gps_position(int d, int m, float s) :
    degrees(d), minutes(m), seconds(s)
    {}
};
W rzeczywistości serializacja jest wtedy dość prosta:
#include <fstream>
std::ofstream ofs("filename.dat", std::ios::binary);

    // create class instance
    const gps_position g(35, 59, 24.567f);

    // save data to archive
    {
        boost::archive::binary_oarchive oa(ofs);
        // write class instance to archive
        oa << g;
        // archive and stream closed when destructors are called
    }

Deserializacja działa w analogiczny sposób.

Istnieją również mechanizmy, które pozwalają obsługiwać serializację wskaźników (złożone struktury danych jak tress itp.nie stanowią problemu), klasy pochodne i można wybierać pomiędzy serializacją binarną i tekstową. Poza tym wszystkie kontenery STL są obsługiwane po wyjęciu z pudełka.

 50
Author: newgre,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2012-07-28 00:43:53

W niektórych przypadkach, mając do czynienia z prostymi typami, można wykonać:

object o;
socket.write(&o, sizeof(o));

To jest w porządku jako proof-of-concept lub first-draft, więc inni członkowie Twojego zespołu mogą nadal pracować nad innymi częściami.

Ale prędzej czy później, zazwyczaj wcześniej , to cię skrzywdzi!

Napotkasz problemy z:

  • wirtualne tabele wskaźników zostaną uszkodzone.
  • wskaźniki (do danych / członków / funkcji) zostaną uszkodzone.
  • różnice w wyściełaniu / wyrównaniu na różnych maszynach.
  • Big / Little-Endian Byte ordering issues.
  • różnice w implementacji float/double.

(dodatkowo musisz wiedzieć, w co się rozpakowujesz po stronie przyjmującej.)

Możesz to poprawić, rozwijając własne metody marshalling/unmarshalling dla każdej klasy. (Najlepiej wirtualne, dzięki czemu mogą być rozszerzane w podklasach.) Kilka prostych makr pozwoli Ci dość szybko wypisać różne podstawowe typy w duży / mały-endian-neutralny porządek.

Ale ten rodzaj pracy gruntowej jest znacznie lepszy i łatwiejszy w obsłudze dzięki bibliotece serializacji Boosta .

 13
Author: Mr.Ree,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2009-02-07 15:17:36

Serializacja oznacza przekształcenie obiektu w dane binarne. Podczas gdy deserializacja oznacza odtworzenie obiektu z danych.

Podczas serializacji przepychasz bajty do wektora uint8_t. Podczas unserializacji odczytujemy bajty z uint8_t wektora.

Są z pewnością wzorce, które można zastosować podczas serializacji rzeczy.

Każda klasa serializowalna powinna mieć serialize(std::vector<uint8_t> &binaryData) lub podobną funkcję signatured, która zapisze jej binarną reprezentację do podanego wektor. Wtedy ta funkcja może przekazać ten wektor w dół do jego funkcji serializujących, więc mogą one również zapisywać swoje rzeczy do niego.

Ponieważ reprezentacja danych może być różna na różnych architekturach. Musisz znaleźć schemat, jak reprezentować dane.

Zacznijmy od podstaw:

Serializowanie danych całkowitych

Po prostu zapisz bajty w małej kolejności endiańskiej. Lub użyj reprezentacji varint, jeśli rozmiar ma znaczenie.

Serializacja w małym porządek endyjski:
data.push_back(integer32 & 0xFF);
data.push_back((integer32 >> 8) & 0xFF);
data.push_back((integer32 >> 16) & 0xFF);
data.push_back((integer32 >> 24) & 0xFF);

Deserializacja z małego porządku endyjskiego:

integer32 = data[0] | (data[1] << 8) | (data[2] << 16) | (data[3] << 24);

Serializowanie danych zmiennoprzecinkowych

Z tego co wiem, IEEE 754 ma tu monopol. Nie znam żadnej mainstreamowej architektury, która używałaby czegoś innego do pływaków. Jedyną rzeczą, która może być inna, jest kolejność bajtów. Niektóre architektury używają little endian, inne używają big endian byte order. Oznacza to, że musisz być ostrożny, w której kolejności głośni się bajty na końcu odbiorczym. Inną różnicą może być Obsługa wartości denormalnych i nieskończoności oraz Nan. Ale tak długo, jak unikasz tych wartości, powinno być OK.

Serializacja:

uint8_t mem[8];
memcpy(mem, doubleValue, 8);
data.push_back(mem[0]);
data.push_back(mem[1]);
...

Deserializacja robi to wstecz. Uważaj na kolejność bajtów swojej architektury!

Ciągi Serializujące

Najpierw musisz uzgodnić kodowanie. UTF-8 jest powszechne. Następnie zapisz go jako długość prefiksu: najpierw przechowujesz długość ciągu za pomocą metody, o której wspomniałem powyżej, a następnie napisz string bajt-by-bajt.

Szeregowanie tablic.

Są takie same jak ciągi. Najpierw serializujesz liczbę całkowitą reprezentującą rozmiar tablicy, a następnie serializujesz każdy obiekt w niej.

Serializacja całych obiektów

Jak już mówiłem, powinni mieć metodę serialize, która dodaje treść do wektora. Aby unserializować obiekt, powinien mieć konstruktor, który pobiera strumień bajtów. Może to być istream, ale w najprostszym przypadku może to być tylko wskaźnik odniesienia uint8_t. Na konstruktor odczytuje żądane bajty ze strumienia i ustawia pola w obiekcie. Jeśli system jest dobrze zaprojektowany i serializuje pola w kolejności pól obiektowych, możesz po prostu przekazać strumień do konstruktorów pola na liście inicjalizacyjnej i deserializować je we właściwej kolejności.

Serializowanie Wykresów obiektowych

Najpierw musisz się upewnić, czy te obiekty są naprawdę czymś, co chcesz serializować. Nie musisz ich serializować, Jeśli wystąpienia tych obiekty obecne na miejscu przeznaczenia.

Teraz odkryłeś, że musisz serializować obiekt wskazywany przez wskaźnik. Problem wskaźników, że są one ważne tylko w programie, który z nich korzysta. Nie można serializować wskaźnika, należy przestać go używać w obiektach. Zamiast tego utwórz pule obiektów. Ta pula obiektów jest w zasadzie tablicą dynamiczną, która zawiera "pola". Te pudełka mają numer referencyjny. Niezerowa liczba referencji wskazuje aktywny obiekt, zero oznacza puste miejsce. Wtedy tworzysz inteligentny wskaźnik podobny do shared_ptr, który nie przechowuje wskaźnika do obiektu, ale indeks w tablicy. Musisz również uzgodnić indeks oznaczający wskaźnik null, np. -1.

Zasadniczo to, co tutaj zrobiliśmy, zastępuje wskaźniki indeksami tablic. Teraz podczas serializacji możesz serializować ten indeks tablicy jak zwykle. Nie musisz się martwić o to, gdzie obiekt będzie w pamięci w systemie docelowym. Upewnij się tylko, że mają ten sam obiekt też.

Więc musimy serializować pule obiektów. Ale które? Cóż, kiedy serializujesz Wykres obiektu, nie serializujesz tylko obiekt, serializujesz cały system. Oznacza to, że serializacja systemu nie powinna zaczynać się od części systemu. Obiekty te nie powinny martwić się o resztę systemu, muszą tylko serializować indeksy tablicy i to wszystko. Powinieneś mieć procedurę serializera systemu, która koordynuje serializację systemu i przechodzi przez odpowiednie baseny obiektów i serializuje je wszystkie.

Na końcu odbiorczym wszystkie tablice i obiekty wewnątrz są deserializowane, odtwarzając żądany Wykres obiektu.

Serializing function pointers

Nie przechowuj wskaźników w obiekcie. Posiada statyczną tablicę zawierającą wskaźniki do tych funkcji i przechowuje indeks w obiekcie.

Ponieważ oba programy mają tabelę skompilowaną do siebie, użycie tylko indeksu powinno praca.

Serializacja typów polimorficznych

Ponieważ powiedziałem, że powinieneś unikać wskaźników w serializowalnych typach i zamiast tego powinieneś używać indeksów tablic, polimorfizm po prostu nie może działać, ponieważ wymaga wskaźników.

Musisz to obejść z tagami typu i związkami.

Wersjonowanie

/ Align = "left" / Możesz potrzebować różnych wersji oprogramowania.

W tym przypadku każdy obiekt powinien wpisać numer wersji na początek ich serializacji w celu wskazania wersji.

Podczas ładowania obiektu po drugiej stronie, nowsze obiekty mogą obsługiwać starsze reprezentacje, ale starsze nie mogą obsługiwać nowszych, więc powinny rzucić wyjątek na ten temat.

Za każdym razem, gdy coś się zmienia, powinieneś podbijać numer wersji.


Aby to zakończyć, serializacja może być złożona. Ale na szczęście nie trzeba serializować wszystkiego w programie, najczęściej serializowane są tylko komunikaty protokołu, które często są zwykłymi starymi strukturami. Więc nie potrzebujesz skomplikowanych sztuczek, o których wspomniałem powyżej zbyt często.

 0
Author: Calmarius,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2018-05-06 23:20:07