Как читать/писать структуру в двоичных файлах?

Я столкнулся с небольшой проблемой. У меня есть структура, у которой есть вектор. Обратите внимание, что вектор является динамическим на каждую итерацию. Теперь, в конкретной итерации, как мне сохранить структуру, которая содержит вектор размера n в двоичном файле?

Кроме того, при получении, предположим, что я знаю, как размер вектора, как я получаю из двоичного файла, структурную переменную, содержащую вектор всех сохраненных элементов?

Я могу хранить что-то в двоичном файле (поскольку я могу видеть размер, увеличивающийся при записи), но когда я пытаюсь вернуть элементы обратно, я получаю размер вектора равным нулю.

К сожалению, я должен достичь этого, используя стандартный STL, и не использовать сторонние библиотеки.

Ответ 1

Вам следует взглянуть на Boost Serialization.

Если вы не можете использовать сторонние библиотеки, вы должны знать, что С++ не поддерживает сериализацию напрямую. Это означает, что вам придется делать это самостоятельно.

В этой статье показан хороший способ сериализации пользовательского объекта на диск и его возврата. И этот учебник показывает вам, как начать прямо сейчас с fstream.

Это моя попытка:

EDIT: поскольку ОП задал вопрос о том, как хранить/извлекать больше, чем запись, я решил обновить исходный код.

Итак, что изменилось? Теперь массив student_t apprentice[3]; хранит информацию трех учеников. Весь массив сериализуется на диск, а затем он загружается обратно в ОЗУ, где возможно чтение/поиск определенных записей. Обратите внимание, что это очень маленький файл (84 байта). Я не предлагаю этот подход при поиске записей в огромных файлах.

#include <fstream>
#include <iostream>
#include <vector>
#include <string.h>

using namespace std;


typedef struct student
{
    char name[10];
    int age;
    vector<int> grades;
}student_t;

int main()
{
    student_t apprentice[3];  
    strcpy(apprentice[0].name, "john");
    apprentice[0].age = 21;
    apprentice[0].grades.push_back(1);
    apprentice[0].grades.push_back(3);
    apprentice[0].grades.push_back(5);    

    strcpy(apprentice[1].name, "jerry");
    apprentice[1].age = 22;
    apprentice[1].grades.push_back(2);
    apprentice[1].grades.push_back(4);
    apprentice[1].grades.push_back(6);

    strcpy(apprentice[2].name, "jimmy");
    apprentice[2].age = 23;
    apprentice[2].grades.push_back(8);
    apprentice[2].grades.push_back(9);
    apprentice[2].grades.push_back(10);

    // Serializing struct to student.data
    ofstream output_file("students.data", ios::binary);
    output_file.write((char*)&apprentice, sizeof(apprentice));
    output_file.close();

    // Reading from it
    ifstream input_file("students.data", ios::binary);
    student_t master[3];
    input_file.read((char*)&master, sizeof(master));         

    for (size_t idx = 0; idx < 3; idx++)
    {
        // If you wanted to search for specific records, 
        // you should do it here! if (idx == 2) ...

        cout << "Record #" << idx << endl;
        cout << "Name: " << master[idx].name << endl;
        cout << "Age: " << master[idx].age << endl;
        cout << "Grades: " << endl;
        for (size_t i = 0; i < master[idx].grades.size(); i++)
           cout << master[idx].grades[i] << " ";
        cout << endl << endl;
    }

    return 0;
}

Результаты

Record #0
Name: john
Age: 21
Grades: 
1 3 5 

Record #1
Name: jerry
Age: 22
Grades: 
2 4 6 

Record #2
Name: jimmy
Age: 23
Grades: 
8 9 10

Дамп двоичного файла:

$ hexdump -c students.data 
0000000   j   o   h   n  \0 237   {  \0   �   �   {   � 025  \0  \0  \0
0000010   (   �   �  \b   4   �   �  \b   8   �   �  \b   j   e   r   r
0000020   y  \0   �  \0   �   �   |  \0 026  \0  \0  \0   @   �   �  \b
0000030   L   �   �  \b   P   �   �  \b   j   i   m   m   y  \0  \0  \0
0000040   �   6   �  \0 027  \0  \0  \0   X   �   �  \b   d   �   �  \b
0000050   h   �   �  \b                                                
0000054

Ответ 2

Обычно вы сериализуете вектор, записывая длину вектора, за которым следует это количество элементов. Когда вы заново прочитаете его, сначала укажите длину, чтобы узнать, сколько еще элементов нужно читать как часть этого вектора. В качестве простого первого приближения рассмотрим что-то вроде этого:

template<class T>
std::ostream &operator<<(std::ostream &output, T const &input) {
    T::size_type size = input.size();

    output << size << "\n";
    std::copy(input.begin(), input.end(), 
         std::ostream_iterator<T::value_type>(output, "\n"));

    return output;
}

template<class T>
std::istream &operator>>(std::istream &input, T &output) {
    T::size_type size, i;

    input >> size;
    output.resize(size);
    std::copy_n(
        std::istream_iterator<t::value_type>(input),
        size,
        output.begin());

    return input;
}

Это доступно для множества настроек, улучшений и простых модификаций - например, на данный момент я передал вектор (или что-то еще - может быть std:: deque и т.д.) по ссылке вместо передачи итераторов. Это, вероятно, упрощает большинство использования, но не подходит для остальных библиотек.

Это также сериализуется в текстовом формате, по одному на строку. Дискуссии, сравнивающие текст с бинарным, произошли раньше, поэтому я не буду пытаться повторить все аргументы здесь. Я просто хочу отметить, что та же основная идея может быть выполнена как в двоичном формате, так и в тексте.