knc
knc

Reputation: 133

Storing vector of variable-size structs in a binary file in c++

I am attempting to read and write a vector of structs into a file. Iterating through each struct, first I write the size of the following struct, then I write the structure. I encounter crashes when reading the file.

Structure:

struct Book {

   string author;
   string title;
   string isbn;
   int copies;
   Status status;

};

Loading from file:

void load_books(vector<Book> *books) {
ifstream readFile;
int read_size;
Book temp;

readFile.open(BOOK_DB_FILE.c_str(), ios::in | ios::binary) ;

while(readFile.read(reinterpret_cast<char*> (&read_size), sizeof(read_size))) {
    cout << read_size << endl;

    if (!readFile.read(reinterpret_cast<char*> (&temp), read_size)) {
        cout << "Failed to read library.dat!" << endl;
    }

    cout << "Attempting to load book..." << endl;
    cout << temp.title << ":" << temp.copies << endl;

    (*books).push_back(temp);

}
cout << "Loaded books!" << endl;

}

Writing to file:

void save_books(vector<Book> *books) {
ofstream writeFile;
int write_size;

writeFile.open(BOOK_DB_FILE.c_str(), ios::out | ios::binary);

for (int ind = 0 ; ind < (*books).size(); ind++) {
    Book book = (*books)[ind];
    write_size = sizeof(book);

    cout << "Writing book with size: " << write_size << endl;
    cout << book.copies << " of " << book.title << " by " << book.author << endl;

    writeFile.write(reinterpret_cast<char*>(&write_size), sizeof(write_size));
    writeFile.write(reinterpret_cast<char*>(books + ind), write_size);
}

}

Upvotes: 0

Views: 1295

Answers (1)

mindriot
mindriot

Reputation: 5678

In general I would always recommend to use a library such as Boost.Serialization, but alas, you're not allowed to use Boost (or any other serialization library, I guess). So we'll look at your current approach instead.

While your method of dumping the binary object layout straight to disk is fraught with risk (e.g. because of platform dependencies like endianness), I'll assume that for a mere class project we can live with these deficiencies.

Your main problem, right now, is that you are dumping the struct Book memory representation to disk and reading it back in, but you are overlooking the fact that the std::string fields are not handled like that.

Internally, std::string is not a POD, but (roughly) another class which allocates separate memory for the actual string data (for sake of illustration, assume it carries a char* pointer in its implementation). You are currently not storing this data; you are only storing the values of std::string's internal pointers, and reading them back when loading. At that point, these internal pointers are pointing nowhere meaningful.

For a rough idea of what you need to do, you will have to explicitly and individually serialize/deserialize all members of your Book class recursively. Roughly like this:

  1. Store the length, and the raw string data of author
  2. Store the length, and the raw string data of title
  3. Store the length, and the raw string data of isbn
  4. Store the integer copies
  5. Store the enum value of status

And when reading, load every member in the same way. So you will need a serialzer/deserializer function for every "basic" type (in your case, std::string, int and Status), and "combining" serializer and deserializer functions for Book which call the base members' serialization/deserialization functions in turn.

This is also roughly what Boost.Serialization does internally, so I recommend you have a look at it anyway. Good luck!

Upvotes: 1

Related Questions