【问题标题】:Reading and writing C++ vector to a file读取和写入 C++ 向量到文件
【发布时间】:2010-03-18 11:51:53
【问题描述】:

对于一些图形工作,我需要尽快读取大量数据,并且理想情况下希望直接读取数据结构并将其写入磁盘。基本上我有各种文件格式的 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));

}

【问题讨论】:

  • 尽量避免使用魔法常量:os.write( &amp;size1, sizeof(size1) ) 比硬编码那里的 4 更好。阅读也是如此。
  • @David,在制作 cmets 之前尽量避免不阅读问题;)
  • @Poita_ :) 我知道更改仅用于压缩,但事实是 4 仅比 sizeof(int) 略小,并且应该始终首选后者,即使在合成代码sn-ps。
  • @David 确实如此。我真的同意,我只是对我的样本很懒
  • 10k 人看了,只有 8 人对接受的答案投了赞成票?

标签: c++ file-io vector


【解决方案1】:

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

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

【讨论】:

  • 读/写填充字节只会减慢你的速度。我会编写一个非常简单的运算符
  • 更不用说在这种情况下使您的文件大 33%。
  • @Jan 读取(大概)一个文本文件并将数字转换为浮点数将比直接读取二进制文件更昂贵,即使它更大。文本文件也可能更大,除非所有值的长度都小于四位。
  • 我正在将多个 3d 模型合并在一起以制作一个模型,这不仅涉及读取多个复杂格式的磁盘文件,而且还涉及大量数学来平移、旋转和缩放坐标,但这只需要做一旦这样缓存是一个很大的收获......虽然我现在已经修改了我的代码以直接读入一个direct3d顶点缓冲区,所以答案不再相关,但我仍然很感兴趣:)
  • @KeithB:我的意思是像 JB 最初那样实现流操作符:ostream&amp; operator&lt;&lt;(ostream&amp; str, Vertex v) { str.write((const char*)&amp;v.x, sizeof(v.x)); ... 在一个充满顶点的文件中,你只需要负责以正确的顺序读取它们,只需和以前一样,但它有助于阅读,并负责填充。
【解决方案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;
}

请注意,我必须在 boost::serialization 命名空间中为您的 Vertex 实现一个 serialize 函数。这让序列化库知道如何序列化Vertex 成员。

我浏览了boost::binary_oarchive 源代码,它似乎直接从流缓冲区读取/写入原始向量数组数据。所以应该很快。

【讨论】:

  • 谢谢。也许对我需要的东西有点矫枉过正,但我​​一定会调查一下
【解决方案3】:

std::vector 保证在内存中是连续的,所以,是的。

【讨论】:

    【解决方案4】:

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

    Boost.Interprocess 库很好地处理了这种方法的一些注意事项。你特别感兴趣的可能是它的allocators and containers

    【讨论】:

      【解决方案5】:

      我刚刚遇到了同样的问题。

      首先,这些陈述是错误的

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

      【讨论】:

        【解决方案6】:

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

        【讨论】:

        • 是的,我会在文件上写一个标题,以确保它只会读回我期望的内容。
        猜你喜欢
        • 1970-01-01
        • 2014-12-29
        • 1970-01-01
        • 1970-01-01
        • 2013-08-02
        • 1970-01-01
        • 2015-07-26
        • 1970-01-01
        • 2012-10-25
        相关资源
        最近更新 更多