Чтение и запись вектора C ++ в файл - PullRequest
17 голосов
/ 18 марта 2010

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

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

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

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

}

Ответы [ 6 ]

20 голосов
/ 18 марта 2010

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

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

10 голосов
/ 18 марта 2010

Возможно, вас заинтересует библиотека 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, и кажется, что он читает / записывает необработанные данные векторного массива непосредственно из / в буфер потока. Так что должно быть довольно быстро.

8 голосов
/ 18 марта 2010

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

4 голосов
/ 29 сентября 2012

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

Во-первых, эти утверждения не работают

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);
2 голосов
/ 18 марта 2010

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

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

1 голос
/ 18 марта 2010

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

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...