2016-04-12 13 views
5

Ich schreibe ein Programm, das regelmäßig Strukturen im unten stehenden Formular speichert und liest.Was ist die einfachste Möglichkeit, Daten von einer Struktur zu und von einer Datei in C++ ohne Serialisierungsbibliothek zu lesen und zu schreiben?

struct Node { 
    int leftChild = 0; 
    int rightChild = 0; 
    std::string value; 
    int count = 1; 
    int balanceFactor = 0; 
}; 

Wie würde ich Knoten in einer Datei lesen und schreiben? Ich möchte die fstream-Klasse mit seekg verwenden und suche die Serialisierung manuell, aber ich bin nicht sicher, wie es funktioniert, basierend auf der Dokumentation und habe Mühe, anständige Beispiele zu finden.

[bearbeiten] angegeben, dass ich keine Serialisierungsbibliothek verwenden möchte.

+1

können Sie boost Serialisierung Bibliothek verwenden, können Sie das Tutorial von http://www.boost.org/doc/libs/1_60_0/libs/serialization/doc/tutorial.html – xiaodong

Antwort

3

Um Objekte serialisieren zu können, müssen Sie sich an das Konzept halten, dass das Objekt seine Mitglieder in den Stream schreibt und Member aus dem Stream liest. Außerdem sollten sich Mitgliederobjekte selbst in den Stream schreiben (und lesen).

implementiert ich ein Schema drei Mitgliedsfunktionen und einen Puffer:

void load_from_buffer(uint8_t * & buffer_pointer); 
void store_to_buffer(uint8_t * & buffer_pointer) const; 
unsigned int size_on_stream() const; 

Die size_on_stream würde zuerst aufgerufen werden, um die Puffergröße für das Objekt (oder wie viel Platz es in dem Puffer besetzt, um zu bestimmen).

Die load_from_buffer Funktion lädt die Mitglieder des Objekts aus einem Puffer mit dem angegebenen Zeiger. Die Funktion inkrementiert den Zeiger auch entsprechend.

Die store_to_buffer-Funktion speichert die Mitglieder der Objekte in einem Puffer mit dem angegebenen Zeiger. Die Funktion inkrementiert den Zeiger auch entsprechend.

Dies kann mithilfe von Vorlagen und Vorlagenspezialisierungen auf POD-Typen angewendet werden.

Mit diesen Funktionen können Sie auch die Ausgabe in den Puffer packen und aus einem gepackten Format laden.

Der Grund für die I/O in den Puffer ist so können Sie die effizienten Block Strom Methoden, wie write und read verwenden.

Edit 1: einen Knoten in einem Stream
Das Problem mit dem Schreiben Schreiben oder einen Knoten (wie eine verknüpfte Liste oder Baumknoten) Serialisierung ist, dass Zeiger nicht übersetzen in eine Datei. Es gibt keine Garantie dafür, dass das Betriebssystem Ihr Programm an demselben Speicherort ablegt oder Ihnen jedes Mal denselben Speicherbereich zur Verfügung stellt.

Sie haben zwei Möglichkeiten: 1) Speichern Sie nur die Daten. 2) Konvertiere die Zeiger in Dateioffsets. Option 2) ist sehr kompliziert, da möglicherweise der Dateizeiger neu positioniert werden muss, da Dateioffsets möglicherweise nicht rechtzeitig bekannt sind.

Beachten Sie auch Datensätze variabler Länge wie Strings. Sie können ein Zeichenfolgenobjekt nicht direkt in eine Datei schreiben. Wenn Sie keine feste Zeichenfolgenbreite verwenden, ändert sich die Zeichenfolgengröße. Sie müssen der Zeichenfolge entweder die Zeichenfolgenlänge voranstellen (bevorzugt) oder eine Art abschließendes Zeichen verwenden, z. B. "\ 0". Die Zeichenfolge Länge zuerst wird bevorzugt, da Sie nicht nach dem Ende der Zeichenfolge suchen müssen; Sie können einen Block lesen, um den Text zu lesen.

4

Dieses Problem ist bekannt als serialization. Verwenden Sie eine Serialisierungsbibliothek wie z. Google's Protocol Buffers oder Flatbuffers.

+0

bekommen kann ich Boost-benutzt habe .Serialisierung in der Vergangenheit zu gemischten Erfolg. – inetknght

+0

Sorry, meine Frage war nicht spezifisch. Ich möchte die Serialisierung selbst machen. –

0

Der Prozess, auf den Sie sich beziehen, wird als Serialisierung bezeichnet. Ich würde Getreide bei http://uscilab.github.io/cereal/

Es unterstützt beidejson, xml und binäre Serialisierung und ist sehr einfach zu bedienen (mit guten Beispielen) empfehlen.

(Leider ist es nicht mein Lieblingsformat yaml unterstützen)

1

Wenn Sie die std :: string durch einen char buffer ersetzen, können Sie fwrite und fread zu schreiben/lesen Sie Ihre Struktur und von der Festplatte verwenden als Informationsblock fester Größe. Innerhalb eines einzigen Programms sollte das funktionieren.

Der große Bug-a-boo ist die Tatsache, dass Compiler Padding zwischen Feldern einfügen, um die Daten ausgerichtet zu halten. Das macht den Code weniger portabel, als ob ein Modul mit unterschiedlichen Ausrichtungsanforderungen kompiliert würde. Die Struktur kann buchstäblich eine andere Größe haben, was Ihre Annahme mit fester Größe aus der Tür wirft.

Ich würde in Richtung einer in der Serialisierungsbibliothek irgendwie getragenen Bibliothek neigen.

1

Ein anderer Ansatz wäre, den Operator < < und den Operator >> für die Struktur zu überlasten, damit er weiß, wie er sich selbst speichert/lädt. Das würde das Problem reduzieren, zu wissen, wo der Knoten zu lesen/schreiben ist. Theoretisch könnten Ihre linken und rechten untergeordneten Felder Suchadressen sein, an denen sich die Knoten tatsächlich befinden, während ein neues Feld die Suchposition des aktuellen Knotens enthalten könnte.

1

Wenn Sie Ihre eigene Serialisierungsmethode implementieren, müssen Sie zuerst entscheiden, ob die Daten auf der Festplatte im Binär- oder Textformat vorliegen sollen.

Ich finde es einfacher, die Möglichkeit zu implementieren, in einem binären Format zu speichern. Die Anzahl der Funktionen, die benötigt werden, um dies zu implementieren, ist gering. Sie müssen Funktionen implementieren, die die grundlegenden Typen, Arrays mit bekannter Größe zur Kompilierzeit, dynamische Arrays und Strings schreiben können. Alles andere kann darüber hinaus gebaut werden.

Hier ist etwas sehr nah an dem, was ich vor kurzem in den Produktionscode eingegeben habe.

#include <cstring> 
#include <fstream> 
#include <cstddef> 
#include <stdexcept> 

// Class to write to a stream 
struct Writer 
{ 
    std::ostream& out_; 

    Writer(std::ostream& out) : out_(out) {} 

    // Write the fundamental types 
    template <typename T> 
     void write(T number) 
     { 
     out_.write(reinterpret_cast<char const*>(&number), sizeof(number)); 
     if (!out_) 
     { 
      throw std::runtime_error("Unable to write a number"); 
     } 
     } 

    // Write arrays whose size is known at compile time 
    template <typename T, uint64_t N> 
     void write(T (&array)[N]) 
     { 
     for(uint64_t i = 0; i < N; ++i) 
     { 
      write(array[i]); 
     } 
     } 

    // Write dynamic arrays 
    template <typename T> 
     void write(T array[], uint64_t size) 
     { 
     write(size); 
     for(uint64_t i = 0; i < size; ++i) 
     { 
      write(array[i]); 
     } 
     } 

    // Write strings 
    void write(std::string const& str) 
    { 
     write(str.c_str(), str.size()); 
    } 

    void write(char const* str) 
    { 
     write(str, std::strlen(str)); 
    } 
}; 

// Class to read from a stream 
struct Reader 
{ 
    std::ifstream& in_; 
    Reader(std::ifstream& in) : in_(in) {} 

    template <typename T> 
     void read(T& number) 
     { 
     in_.read(reinterpret_cast<char*>(&number), sizeof(number)); 
     if (!in_) 
     { 
      throw std::runtime_error("Unable to read a number."); 
     } 
     } 

    template <typename T, uint64_t N> 
     void read(T (&array)[N]) 
     { 
     for(uint64_t i = 0; i < N; ++i) 
     { 
      read(array[i]); 
     } 
     } 

    template <typename T> 
     void read(T*& array) 
     { 
     uint64_t size; 
     read(size); 
     array = new T[size]; 
     for(uint64_t i = 0; i < size; ++i) 
     { 
      read(array[i]); 
     } 
     } 

    void read(std::string& str) 
    { 
     char* s; 
     read(s); 
     str = s; 
     delete [] s; 
    } 
}; 

// Test the code. 

#include <iostream> 

void writeData(std::string const& file) 
{ 
    std::ofstream out(file); 
    Writer w(out); 
    w.write(10); 
    w.write(20.f); 
    w.write(200.456); 
    w.write("Test String"); 
} 

void readData(std::string const& file) 
{ 
    std::ifstream in(file); 
    Reader r(in); 

    int i; 
    r.read(i); 
    std::cout << "i: " << i << std::endl; 

    float f; 
    r.read(f); 
    std::cout << "f: " << f << std::endl; 

    double d; 
    r.read(d); 
    std::cout << "d: " << d << std::endl; 

    std::string s; 
    r.read(s); 
    std::cout << "s: " << s << std::endl; 
} 

void testWriteAndRead(std::string const& file) 
{ 
    writeData(file); 
    readData(file); 
} 

int main() 
{ 
    testWriteAndRead("test.bin"); 
    return 0; 
} 

Ausgang:

i: 10 
f: 20 
d: 200.456 
s: Test String 

Die Fähigkeit, ein Node ist sehr einfach zu implementieren, zu schreiben und zu lesen.

void write(Writer& w, Node const& n) 
{ 
    w.write(n.leftChild); 
    w.write(n.rightChild); 
    w.write(n.value); 
    w.write(n.count); 
    w.write(n.balanceFactor); 
} 

void read(Reader& r, Node& n) 
{ 
    r.read(n.leftChild); 
    r.read(n.rightChild); 
    r.read(n.value); 
    r.read(n.count); 
    r.read(n.balanceFactor); 
}