web-dev-qa-db-ja.com

C ++ベクトルの読み取りとファイルへの書き込み

一部のグラフィックス作業では、大量のデータをできるだけ早く読み取る必要があり、理想的にはデータ構造を直接ディスクに読み書きしたいと考えています。基本的に、ロードに時間がかかりすぎるさまざまなファイル形式の3Dモデルをロードしているので、プログラムの後続の実行ではるかに高速にロードされるキャッシュとして、「準備された」形式でそれらを書き出したいと思います。

このようにしても大丈夫ですか?私の心配は、ベクトルのデータを直接読み取ることです。エラーチェックを削除し、intのサイズとして4をハードコーディングするなどして、簡単な例を示します。これは悪いコードです。私の質問は、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));

}
17
jcoder

Laurynasが言うように、_std::vector_は連続していることが保証されているので、機能するはずですが、移植性がない可能性があります。

ほとんどのシステムでは、sizeof(Vertex)は12になりますが、構造体が埋め込まれることは珍しくありません。そのため、sizeof(Vertex) == 16になります。あるシステムでデータを書き込んでから、そのファイルを別のシステムで読み込む場合、それが正しく機能するという保証はありません。

20
Peter Alexander

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名前空間のserializeVertex関数を実装する必要があることに注意してください。これにより、シリアル化ライブラリはVertexメンバーをシリアル化する方法を知ることができます。

boost::binary_oarchiveソースコードを参照しましたが、ストリームバッファとの間で生のベクトル配列データを直接読み書きしているようです。だからそれはかなり速いはずです。

10
Emile Cormier

std::vectorはメモリ内で継続することが保証されているので、そうです。

8

私はこれとまったく同じ問題に遭遇しました。

まず、これらのステートメントは壊れています

os.write((const char*)&list[0], size1 * sizeof(Vertex));
is.read((char*)&list2[0], size2 * sizeof(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
mortonjt

ファイルとの間でvector<>を明示的に読み書きする別の方法は、基になるアロケータを、メモリマップトファイルからメモリを割り当てるアロケータに置き換えることです。これにより、読み取り/書き込み関連の中間コピーを回避できます。ただし、このアプローチにはある程度のオーバーヘッドがあります。ファイルが非常に大きくない限り、特定のケースでは意味がない場合があります。通常どおりプロファイルを作成して、このアプローチが適切かどうかを判断します。

このアプローチには、 Boost.Interprocess ライブラリによって非常にうまく処理されるいくつかの警告もあります。特に興味深いのは、その アロケータとコンテナ です。

2
Void

これが同じコードによるキャッシュに使用されている場合、これに問題はありません。私はこれと同じ手法を複数のシステムで問題なく使用しました(すべてUnixベース)。追加の予防策として、ファイルの先頭に既知の値を使用して構造体を記述し、それが正常であることを確認することをお勧めします。構造体のサイズをファイルに記録することもできます。これにより、パディングが変更された場合に、将来的にデバッグ時間を大幅に節約できます。

1
KeithB