Ist es möglich, eine Klasse in C ++ zu serialisieren und zu deserialisieren?

138

Ist es möglich, eine Klasse in C ++ zu serialisieren und zu deserialisieren?

Ich benutze Java seit 3 ​​Jahren und Serialisierung / Deserialisierung ist in dieser Sprache ziemlich trivial. Hat C ++ ähnliche Funktionen? Gibt es native Bibliotheken, die die Serialisierung übernehmen?

Ein Beispiel wäre hilfreich.

Agusti-N
quelle
2
Sie sind sich nicht sicher, was Sie unter "native" verstehen. Meinen Sie natives C ++ (wie Boost.Serialization)? Meinen Sie nur die C ++ Standard Library? Meinst du etwas anderes?
Jwfearn
1
Ich meine "keine externe Software-Bibliothek". Und sorry mein Englisch ist nicht sehr gut: S. Ich bin aus Argentinien
Agusti-N
3
Es gibt keine native Möglichkeit, ein Objekt zu serialisieren (Sie können die Binärdaten immer noch von einem POD sichern, aber Sie erhalten nicht das, was Sie wollen). Obwohl Boost keine "interne Bibliothek" ist, ist es die erste externe Bibliothek, die Sie in Betracht ziehen sollten, um sie Ihrem Compiler hinzuzufügen. Boost ist von STL-Qualität (dh Top Gun C ++)
paercebal

Antworten:

95

Die Boost::serializationBibliothek geht damit eher elegant um. Ich habe es in mehreren Projekten verwendet. Es gibt ein Beispielprogramm, das zeigt , wie es zu benutzen, hier .

Die einzige native Möglichkeit besteht darin, Streams zu verwenden. Das ist im Wesentlichen alles, was die Boost::serializationBibliothek tut. Sie erweitert die Stream-Methode, indem sie ein Framework zum Schreiben von Objekten in ein textähnliches Format und zum Lesen aus demselben Format einrichtet.

Für integrierte Typen oder Ihre eigenen Typen mit operator<<und operator>>richtig definiert ist das ziemlich einfach. Weitere Informationen finden Sie in den C ++ - FAQ .

Head Geek
quelle
Mir scheint, dass die Boost :: Serialisierung erfordert, dass der Aufrufer die Reihenfolge verfolgt, in der Objekte geschrieben und gelesen werden. Ist das korrekt? Wenn sich also die Reihenfolge ändert, in der zwei Felder zwischen den Versionen eines Programms geschrieben werden, liegt eine Inkompatibilität vor. Ist das richtig?
Agnel Kurian
1
Dies liegt wahrscheinlich an den Serialisierungsfunktionen, nicht am Boost :: Serialisierungscode selbst.
Head Geek
1
@ 0xDEADBEEF: Das ist wahrscheinlich passiert, wenn ein Binärarchiv (i | o) verwendet wird, das andere "Probleme" wie Endian-Ness einführt. Versuchen Sie es mit dem Archiv text_ (i | o), es ist plattformunabhängiger.
Ela782
2
Eine bestimmte Rahmen- / Bibliothekslösung sollte nicht die akzeptierte Antwort sein.
Andrea
3
@Andrea: Die Boost-Bibliothek ist ein Sonderfall. Bis zur Fertigstellung von C ++ 11 war es so gut wie unmöglich, modernen C ++ - Code ohne C ++ zu schreiben. Daher war es näher an einer sekundären STL als an einer separaten Bibliothek.
Head Geek
52

Mir ist klar, dass dies ein alter Beitrag ist, aber er ist einer der ersten, der bei der Suche auftaucht c++ serialization.

Ich empfehle jedem, der Zugriff auf C ++ 11 hat, einen Blick auf cereal zu werfen , eine C ++ 11-Header-Bibliothek für die Serialisierung, die sofort Binär-, JSON- und XML-Dateien unterstützt. Getreide wurde so konzipiert, dass es einfach zu erweitern und zu verwenden ist und eine ähnliche Syntax wie Boost hat.

Azoth
quelle
4
Das Gute an Getreide ist, dass es im Gegensatz zu Boost nur minimale Metadaten enthält (fast keine). boost :: serialization wird sehr ärgerlich, wenn jedes Mal, wenn Sie ein Archiv öffnen, die lib-Version in den Stream geschrieben wird, wodurch das Anhängen an eine Datei unmöglich wird.
CyberSnoopy
@CyberSnoopy - Es gibt ein Flag zum Unterdrücken dieser Funktion beim Erstellen eines Archivs. Natürlich müssen Sie sich auch beim Lesen des Archivs daran erinnern.
Robert Ramey
16

Boost ist ein guter Vorschlag. Aber wenn Sie Ihre eigenen rollen möchten, ist es nicht so schwer.

Grundsätzlich benötigen Sie nur eine Möglichkeit, ein Diagramm von Objekten zu erstellen und diese dann in einem strukturierten Speicherformat (JSON, XML, YAML usw.) auszugeben. Das Erstellen des Diagramms ist so einfach wie das Verwenden eines markierungsrekursiven Algorithmus für anständige Objekte und das anschließende Ausgeben aller markierten Objekte.

Ich habe einen Artikel geschrieben, der ein rudimentäres (aber immer noch leistungsfähiges) Serialisierungssystem beschreibt. Vielleicht finden Sie es interessant: Verwenden von SQLite als On-Disk-Dateiformat, Teil 2 .

Frank Krueger
quelle
14

Was "eingebaute" Bibliotheken angeht, sind die <<und >>speziell für die Serialisierung reserviert.

Sie sollten überschreiben, <<um Ihr Objekt in einem Serialisierungskontext (normalerweise einem iostream) auszugeben und >>Daten aus diesem Kontext zurückzulesen. Jedes Objekt ist für die Ausgabe seiner aggregierten untergeordneten Objekte verantwortlich.

Diese Methode funktioniert einwandfrei, solange Ihr Objektdiagramm keine Zyklen enthält.

Wenn dies der Fall ist, müssen Sie eine Bibliothek verwenden, um diese Zyklen zu verarbeiten.

Frank Krueger
quelle
3
Das kann sicherlich nicht richtig sein ... Die implementierten <<Operatoren werden verwendet, um von Menschen lesbare Textdarstellungen von Objekten zu drucken, was sehr oft nicht das ist, was Sie für die Serialisierung wollen.
Einpoklum
1
@einpoklum Versuchen Sie, <<das Generikum nicht für das Generikum ostream, sondern für einen Dateistream zu definieren.
Carcigenicate
1
@Carcigenicate: Eine Protokolldatei, die für Menschen lesbaren Text enthält, ist ein Dateistream.
Einpoklum
1
@einpoklum Ich bin mir nicht ganz sicher, was du meinst. Frank hat recht, diese Operatoren können zum Serialisieren verwendet werden. Ich habe sie nur definiert, um einen Vektor zu serialisieren / deserialisieren.
Carcigenicate
2
Ich denke, der Haken ist hier: "Sie sollten überschreiben, <<um Ihr Objekt in einem Serialisierungskontext auszugeben ... Jedes Objekt ist für die Ausgabe seines ... verantwortlich." - Die Frage ist, wie Sie vermeiden können, dass Sie dies mühsam für jedes Objekt aufschreiben müssen: Wie viel kann das Sprache oder Bibliotheken helfen?
ShreevatsaR
14

Ich empfehle Google- Protokollpuffer . Ich hatte die Möglichkeit, die Bibliothek an einem neuen Projekt zu testen, und es ist bemerkenswert einfach zu bedienen. Die Bibliothek ist stark auf Leistung optimiert.

Protobuf unterscheidet sich von anderen hier erwähnten Serialisierungslösungen darin, dass es Ihre Objekte nicht serialisiert, sondern Code für Objekte generiert, die gemäß Ihrer Spezifikation serialisiert werden.

yoav.aviram
quelle
2
Haben Sie Erfahrung damit, Objekte mit einer Größe von 10 bis 50 MB damit zu serialisieren? Die Dokumentation scheint zu sagen, dass Protokollpuffer am besten für Objekte mit einer Größe von etwa MB geeignet sind.
Agnel Kurian
Ich habe meine eigene Bibliothek gerollt, benutze (noch) keine Streams, also ist es wirklich für kleine Dinge: gist.github.com/earonesty/5ba3a93f391ea03ef90884840f068767
Erik Aronesty
13

Boost :: Serialisierung ist eine großartige Option, aber ich bin auf ein neues Projekt gestoßen: Getreide, das ich viel eleganter finde! Ich empfehle dringend, es zu untersuchen.

M2tM
quelle
4

Sie können das amef- Protokoll überprüfen . Ein Beispiel für die C ++ - Codierung in amef wäre wie folgt:

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

Dekodierung in Java wäre wie,

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

Die Protokollimplementierung enthält Codecs für C ++ und Java. Der interessante Teil ist, dass die Objektklassendarstellung in Form von Name-Wert-Paaren beibehalten werden kann. Ich habe in meinem letzten Projekt ein ähnliches Protokoll benötigt, als ich zufällig auf dieses Protokoll gestoßen bin, das ich tatsächlich hatte Die Basisbibliothek wurde gemäß meinen Anforderungen geändert. Hoffe das hilft dir.

Dave
quelle
3

Süß bestehen ist eine andere.

Es ist möglich, zu und von Streams in den Formaten XML, JSON, Lua und Binär zu serialisieren.

Vincent
quelle
Diese Seite scheint
ausgefallen
2

Ich schlage vor, sich mit abstrakten Fabriken zu befassen, die häufig als Grundlage für die Serialisierung dienen

Ich habe in einer anderen SO-Frage zu C ++ - Fabriken geantwortet. Bitte sehen Sie dort, ob eine flexible Fabrik von Interesse ist. Ich versuche, einen alten Weg von ET ++ zu beschreiben, um Makros zu verwenden, der für mich großartig funktioniert hat.

ET ++ war ein Projekt, um alte MacApp auf C ++ und X11 zu portieren. In der Anstrengung begann Eric Gamma usw. über Designmuster nachzudenken . ET ++ enthielt automatische Möglichkeiten zur Serialisierung und Selbstbeobachtung zur Laufzeit.

Epatel
quelle
0

Wenn Sie eine einfache und optimale Leistung wünschen und sich nicht um die Abwärtsdatenkompatibilität kümmern, versuchen Sie es mit HPS , das leichtgewichtig, viel schneller als Boost usw. und viel einfacher zu verwenden als Protobuf usw. ist.

Beispiel:

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
quelle
0

Hier ist eine einfache Serializer-Bibliothek, die ich aufgerissen habe. Es ist nur der Header c11 und enthält Beispiele für die Serialisierung von Basistypen. Hier ist eine für eine Karte zum Unterricht.

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

Ausgabe:

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
}
Neil McGill
quelle
0

Ich verwende die folgende Vorlage, um die Serialisierung zu implementieren:

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

Hier Tist der Typ, den Sie serialisieren möchten, Modeein Dummy-Typ, um zwischen verschiedenen Arten der Serialisierung zu unterscheiden, z. Die gleiche Ganzzahl kann als Little Endian, Big Endian, Varint usw. serialisiert werden.

Standardmäßig Serializerdelegiert die Aufgabe an das zu serialisierende Objekt. Für eingebaute Typen sollten Sie eine Vorlagenspezialisierung der vornehmen Serializer.

Vorlagen für Komfortfunktionen werden ebenfalls bereitgestellt.

Zum Beispiel Little-Endian-Serialisierung von Ganzzahlen ohne Vorzeichen:

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

Dann zu serialisieren:

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

Deserialisieren:

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

Aufgrund der abstrakten Iteratorlogik sollte es mit jedem Iterator (z. B. Stream-Iteratoren), Zeiger usw. funktionieren.

Calmarius
quelle