读写C ++向量到文件

时间:2010-03-18 11:51:53

标签: c++ file-io vector

对于某些图形工作,我需要尽快读取大量数据,理想情况下是直接读取和写入数据结构到磁盘。基本上我有各种各样的文件格式的3d模型,加载时间太长,所以我想把它们以“准备好”的格式写出来作为缓存,在后续的程序运行中加载速度要快得多。

这样做是否安全? 我担心的是直接读入载体的数据?我已经删除了错误检查,硬编码4作为int的大小等等,所以我可以举一个简短的工作示例,我知道这是错误的代码,我的问题是,如果在c ++中读取整个数组是安全的结构直接进入这样的矢量?我相信它是这样的,但是当你开始进入低级并直接处理像这样的原始内存时,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 个答案:

答案 0 :(得分:20)

正如Laurynas所说,std::vector保证是连续的,所以应该可行,但它可能是不可移植的。

在大多数系统上,sizeof(Vertex)将为12,但结构填充并不罕见,因此sizeof(Vertex) == 16。如果您要在一个系统上写入数据,然后在另一个系统上读取该文件,则无法保证它能正常工作。

答案 1 :(得分:10)

您可能对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源代码,它似乎直接从/向流缓冲区读取/写入原始向量数组数据。所以它应该很快。

答案 2 :(得分:8)

std::vector保证在记忆中是连续的,所以,是的。

答案 3 :(得分: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);

答案 4 :(得分:2)

从文件中显式读取和写入vector<>的另一种方法是将底层分配器替换为从内存映射文件中分配内存的分配器。这将允许您避免中间读/写相关副本。但是,这种方法确实有一些开销。除非您的文件非常大,否则对您的特定情况可能没有意义。像往常一样来确定这种方法是否合适。

这种方法也有一些注意事项,Boost.Interprocess库可以很好地处理这些问题。您特别感兴趣的可能是allocators and containers

答案 5 :(得分:1)

如果使用相同的代码进行缓存,我认为没有任何问题。我在多个系统上使用了相同的技术而没有任何问题(所有基于Unix的)。作为额外的预防措施,您可能希望在文件的开头编写一个具有已知值的结构,并检查它是否正常。您可能还想在文件中记录结构的大小。如果填充变化,这将在未来节省大量的调试时间。