Подтвердить что ты не робот

Чтение и запись вектора С++ в файл

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

Безопасно ли это сделать так? Мои заботы вокруг прямого чтения в данные вектора? Я удалил проверку ошибок, жестко закодировал 4 как размер int и т.д., Чтобы я мог дать короткий рабочий пример, я знаю, что это плохой код, мой вопрос в том, действительно ли в С++ читать целый массив структур непосредственно в такой вектор? Я считаю, что это так, но С++ имеет так много ловушек и undefined behavour, когда вы начинаете идти на низкий уровень и напрямую работаете с необработанной памятью.

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

#include <fstream>
#include <vector>

using namespace std;

struct Vertex
{
    float x, y, z;
};

typedef vector<Vertex> VertexList;

int main()
{
    // Create a list for testing
    VertexList list;
    Vertex v1 = {1.0f, 2.0f,   3.0f}; list.push_back(v1);
    Vertex v2 = {2.0f, 100.0f, 3.0f}; list.push_back(v2);
    Vertex v3 = {3.0f, 200.0f, 3.0f}; list.push_back(v3);
    Vertex v4 = {4.0f, 300.0f, 3.0f}; list.push_back(v4);

    // Write out a list to a disk file
    ofstream os ("data.dat", ios::binary);

    int size1 = list.size();
    os.write((const char*)&size1, 4);
    os.write((const char*)&list[0], size1 * sizeof(Vertex));
    os.close();


    // Read it back in
    VertexList list2;

    ifstream is("data.dat", ios::binary);
    int size2;
    is.read((char*)&size2, 4);
    list2.resize(size2);

     // Is it safe to read a whole array of structures directly into the vector?
    is.read((char*)&list2[0], size2 * sizeof(Vertex));

}
4b9b3361

Ответ 1

Как говорит Лауринас, std::vector гарантированно будет смежным, поэтому он должен работать, но он потенциально не переносится.

В большинстве систем sizeof(Vertex) будет 12, но это не редкость для того, чтобы структура была дополнена, так что sizeof(Vertex) == 16. Если вы должны были записать данные в одну систему, а затем прочитать этот файл на другом, нет гарантии, что он будет работать правильно.

Ответ 2

Вам может быть интересна библиотека Boost.Serialization. Он знает, как сохранять/загружать контейнеры STL на/с диска, между прочим. Это может быть излишним для вашего простого примера, но это может стать более полезным, если вы выполняете другие типы сериализации в своей программе.

Вот пример кода, который выполняет то, что вы ищете:

#include <algorithm>
#include <fstream>
#include <vector>
#include <boost/archive/binary_oarchive.hpp>
#include <boost/archive/binary_iarchive.hpp>
#include <boost/serialization/vector.hpp>

using namespace std;

struct Vertex
{
    float x, y, z;
};

bool operator==(const Vertex& lhs, const Vertex& rhs)
{
    return lhs.x==rhs.x && lhs.y==rhs.y && lhs.z==rhs.z;
}

namespace boost { namespace serialization {
    template<class Archive>
    void serialize(Archive & ar, Vertex& v, const unsigned int version)
    {
        ar & v.x; ar & v.y; ar & v.z;
    }
} }

typedef vector<Vertex> VertexList;

int main()
{
    // Create a list for testing
    const Vertex v[] = {
        {1.0f, 2.0f,   3.0f},
        {2.0f, 100.0f, 3.0f},
        {3.0f, 200.0f, 3.0f},
        {4.0f, 300.0f, 3.0f}
    };
    VertexList list(v, v + (sizeof(v) / sizeof(v[0])));

    // Write out a list to a disk file
    {
        ofstream os("data.dat", ios::binary);
        boost::archive::binary_oarchive oar(os);
        oar << list;
    }

    // Read it back in
    VertexList list2;

    {
        ifstream is("data.dat", ios::binary);
        boost::archive::binary_iarchive iar(is);
        iar >> list2;
    }

    // Check if vertex lists are equal
    assert(list == list2);

    return 0;
}

Обратите внимание, что мне пришлось реализовать функцию serialize для вашего Vertex в пространстве имен boost::serialization. Это позволяет библиотеке сериализации знать, как сериализовать элементы Vertex.

Я просмотрел исходный код boost::binary_oarchive, и кажется, что он читает/записывает данные необработанного векторного массива непосредственно из/в буфер потока. Так что это должно быть довольно быстро.

Ответ 3

std::vector гарантированно будет непрерывным в памяти, поэтому да.

Ответ 4

Я столкнулся с этой же проблемой.

Во-первых, эти утверждения сломаны

os.write((const char*)&list[0], size1 * sizeof(Vertex));
is.read((char*)&list2[0], size2 * sizeof(Vertex));

В структуре данных Vector есть другой материал, поэтому это сделает ваш новый вектор заполнен мусором.

Решение:
Когда вы пишете свой вектор в файл, не беспокойтесь о размере вашего класса Vertex, просто напишите весь вектор в памяти.

os.write((const char*)&list, sizeof(list));

И затем вы можете сразу прочитать весь вектор в памяти

is.seekg(0,ifstream::end);
long size2 = is.tellg();
is.seekg(0,ifstream::beg);
list2.resize(size2);
is.read((char*)&list2, size2);

Ответ 5

Другая альтернатива явному чтению и записи вашего vector<> из файла и в файл - заменить базовый распределитель на тот, который выделяет память из файла с отображением памяти. Это позволит вам избежать промежуточной копии, связанной с чтением/записью. Однако этот подход имеет некоторые накладные расходы. Если ваш файл не очень большой, он может не иметь смысла для вашего конкретного случая. Профиль, как обычно, определяет, подходит ли этот подход.

Есть также некоторые оговорки к этому подходу, которые очень хорошо обрабатываются библиотекой Boost.Interprocess. Особый интерес для вас может быть его распределители и контейнеры.

Ответ 6

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