11

ここでの使用法は、read() を C++ std:vector に直接使用する場合と同じですが、再割り当ての数があります。

入力ファイルのサイズが不明なため、ファイル サイズがバッファ サイズを超えると、サイズを 2 倍にしてバッファを再割り当てします。これが私のコードです:

#include <vector>
#include <fstream>
#include <iostream>

int main()
{
    const size_t initSize = 1;
    std::vector<char> buf(initSize); // sizes buf to initSize, so &buf[0] below is valid
    std::ifstream ifile("D:\\Pictures\\input.jpg", std::ios_base::in|std::ios_base::binary);
    if (ifile)
    {
        size_t bufLen = 0;
        for (buf.reserve(1024); !ifile.eof(); buf.reserve(buf.capacity() << 1))
        {
            std::cout << buf.capacity() << std::endl;
            ifile.read(&buf[0] + bufLen, buf.capacity() - bufLen);
            bufLen += ifile.gcount();
        }
        std::ofstream ofile("rebuild.jpg", std::ios_base::out|std::ios_base::binary);
        if (ofile)
        {
            ofile.write(&buf[0], bufLen);
        }
    }
}

プログラムは期待どおりにベクトル容量を出力し、出力ファイルを入力と同じサイズで書き込みますが、オフセットinitSizeの前は入力と同じバイトのみで、その後はすべてゼロになります...

&buf[bufLen]inの使用read()は間違いなく未定義の動作ですが&buf[0] + bufLen、継続的な割り当てが保証されているため、適切な書き込み位置が得られますね。( が提供されます。サイズが になることにinitSize != 0注意してください。また、はい、私の環境で の場合、ランタイムの致命的なエラーが発生します。) 何か見逃していますか? これもUBですか?標準は std::vector のこの使用法について何か言っていますか?std::vector<char> buf(initSize);bufinitSizeinitSize == 0

はい、最初にファイル サイズを計算し、まったく同じバッファー サイズを割り当てることができることはわかっていますが、私のプロジェクトでは、入力ファイルがほぼ常に特定の よりも小さいことが予想されるSIZEため、 に設定initSizeしてSIZE、オーバーヘッドはないと予想できます (ファイルサイズの計算など)、「例外処理」のためだけに再割り当てを使用します。そして、はい、私は と を置き換えることができることを知っています。そうすれば、reserve()少しresize()capacity()オーバーヘッドsize()(サイズ変更のたびにバッファーをゼロにする) で物事が機能しますが、冗長な操作、一種の偏執症を取り除きたいと思っています...

更新 1:

実際、適切な位置を取得する標準から論理的に推測できます。&buf[0] + bufLen

std::vector<char> buf(128);
buf.reserve(512);
char* bufPtr0 = &buf[0], *bufPtrOutofRange = &buf[0] + 200;
buf.resize(256); std::cout << "standard guarantees no reallocation" << std::endl;
char* bufPtr1 = &buf[0], *bufInRange = &buf[200]; 
if (bufPtr0 == bufPtr1)
    std::cout << "so bufPtr0 == bufPtr1" << std::endl;
std::cout << "and 200 < buf.size(), standard guarantees bufInRange == bufPtr1 + 200" << std::endl;
if (bufInRange == bufPtrOutofRange)
    std::cout << "finally we have: bufInRange == bufPtrOutofRange" << std::endl;

出力:

standard guarantees no reallocation
so bufPtr0 == bufPtr1
and 200 < buf.size(), standard guarantees bufInRange == bufPtr1 + 200
finally we have: bufInRange == bufPtrOutofRange

そして、ここで 200 は、every に置き換えることができbuf.size() <= i < buf.capacity()、同様の演繹が成り立ちます。

更新された 2:

はい。今日、問題を調べる時間がありました。プログラムは正しいアドレスを取得し、正しいデータを予約済みメモリに書き込みましたが、次のreserve()ではbuf再割り当てされ、範囲内の要素のみが[0, buf.size())新しいメモリにコピーされました。これがなぞなぞの答えです...

最後の注意:バッファーがデータでいっぱいになった後に再割り当てする必要がない場合は、reserve()/capatity()の代わりに間違いなく使用できresize()/size()ますが、必要な場合は後者を使用してください。また、ここで利用可能なすべての実装 (VC++、g++、ICC) で、例は期待どおりに動作します。

const size_t initSize = 1;
std::vector<char> buf(initSize);
buf.reserve(1024*100); // assume the reserved space is enough for file reading
std::ifstream ifile("D:\\Pictures\\input.jpg", std::ios_base::in|std::ios_base::binary);
if (ifile)
{
    ifile.read(&buf[0], buf.capacity());  // ok. the whole file is read into buf
    std::ofstream ofile("rebuld.jpg", std::ios_base::out|std::ios_base::binary);
    if (ofile)
    {
        ofile.write(&buf[0], ifile.gcount()); // rebuld.jpg just identical to input.jpg
    }
}
buf.reserve(1024*200); // horror! probably always lose all data in buf after offset initSize

'TC++PL, 4e' pp 1041 から引用した別の例を次に示します。関数の最初の行では ではreserve()なくを使用していることに注意してresize()ください。

void fill(istream& in, string& s, int max)
// use s as target for low-level input (simplified)
{
    s.reserve(max); // make sure there is enough allocated space
    in.read(&s[0],max);
    const int n = in.gcount(); // number of characters read
    s.resize(n);
    s.shrink_to_fit();  // discard excess capacity
}

更新 3 (8 年後): この数年間で多くのことが起こりました。私は 6 年近く C++ を作業言語として使用していませんでしたが、現在は博士課程の学生です! また、多くの人が UB があると考えていますが、彼らが示した理由はかなり異なり (一部はすでに UB ではないことが示されています)、これは複雑なケースであることを示しています。そのため、投票して回答を書く前に、コメントを読んで参加することを強くお勧めします

もう 1 つのことは、博士課程のトレーニングを受けて、今では比較的簡単に C++ 標準に飛び込むことができるようになったことです。標準に基づいて、上記の2つのコードブロックが機能するはずであることを私自身の回答で示したと思います。(このstring例では C++11 が必要です。) 私の回答はまだ論争の的になっているため (ただし、改ざんされていないと私は信じています)、私はそれを受け入れず、むしろ批判的なレビューやその他の回答に対してオープンです。

4

2 に答える 2

5

reserve実際にベクターにスペースを追加するのではなく、サイズを変更するときに再割り当てが必要ないことを確認するだけです。reserveを使用する代わりに を使用し、実際に読み取ったバイト数がわかったらresize、最後に実行します。resize

保証されてreserveいるのは、ベクトルのサイズを最大capacity(). の一部でない限り、これらの予約済みバイトの内容を維持することは保証されませsize()ん。

たとえば、Debug フラグを使用してビルドされたコードには、バグを見つけやすくするための追加機能が含まれているのが一般的です。おそらく、新しく割り当てられたメモリは、明確に定義されたパターンで満たされるでしょう。そしておそらく、クラスはそのメモリを定期的にスキャンして変更されているかどうかを確認し、バグだけがその変更を引き起こした可能性があるという仮定の下で例外をスローします。そのような実装は、依然として標準に準拠しています。

の例はstd::string、ほぼ確実に失敗する場合があるため、さらに優れています。 string::c_str()最後に null ターミネータ文字を含む文字列へのポインタを返します。現在、適合する実装は、終端の null のための余地のある 2 番目のバッファーを割り当て、文字列をコピーした後にそのポインターを返すことができますが、それは非常に無駄です。文字列クラスは、予約済みバッファに追加の null 文字用のスペースがあることを確認し、必要に応じてそこに null を書き込む可能性が高くなります。ただし、標準では、その null がいつ書き込まれるかは規定されていません。それは、への呼び出しであるc_strか、文字列が変更される可能性のある任意の時点である可能性があります。そのため、バイトの 1 つがいつ上書きされるかを知る方法はありません。

初期化されていないバイトのバッファが本当に必要な場合std::vector<char>は、おそらく間違ったツールです。std::unique_ptr<char>代わりになどのスマート ポインターを確認する必要があります。

于 2013-10-02T04:13:13.633 に答える