web-dev-qa-db-ja.com

C ++でファイル全体をstd :: stringに読み込む最良の方法は何ですか?

ファイルを_std::string_に読み込むには、つまり、ファイル全体を一度に読み込むにはどうすればよいですか?

テキストモードまたはバイナリモードは、呼び出し元が指定する必要があります。ソリューションは、標準に準拠し、ポータブルで効率的でなければなりません。文字列のデータを不必要にコピーしてはならず、文字列の読み取り中にメモリの再割り当てを避ける必要があります。

これを行う1つの方法は、ファイルサイズを統計し、_std::string_およびfread()のサイズを_std::string_のconst_cast<char*>() 'ed data()に変更することです。 。これには_std::string_のデータが連続している必要がありますが、これは標準では必要ありませんが、すべての既知の実装に当てはまるようです。さらに悪いことに、ファイルがテキストモードで読み取られる場合、_std::string_のサイズはファイルのサイズと等しくない場合があります。

_std::ifstream_のrdbuf()を使用して_std::ostringstream_に、そこから_std::string_に完全に正しい、標準に準拠した移植可能なソリューションを構築できます。ただし、これにより文字列データがコピーされたり、メモリが不必要に再割り当てされたりする可能性があります。関連するすべての標準ライブラリの実装は、すべての不要なオーバーヘッドを回避するのに十分スマートですか?別の方法がありますか?目的の機能を既に提供しているいくつかの隠されたBoost機能を逃しましたか?

実装方法を提案してください。

_void Slurp(std::string& data, bool is_binary)
_

上記の説明を考慮してください。

149
wilbur_m

そして最速(私が知っている、メモリマップファイルの割引):

std::string str(static_cast<std::stringstream const&>(std::stringstream() << in.rdbuf()).str());

これには、文字列ストリーム用の追加ヘッダー<sstream>が必要です。 (static_castは古いoperator <<を返すため、ostream&が必要ですが、実際にはstringstream&であるため、キャストは安全です。)

複数行に分割し、一時変数を変数に移動すると、より読みやすいコードが得られます。

std::string Slurp(std::ifstream& in) {
    std::stringstream sstr;
    sstr << in.rdbuf();
    return sstr.str();
}

または、もう一度1行で:

std::string Slurp(std::ifstream& in) {
    return static_cast<std::stringstream const&>(std::stringstream() << in.rdbuf()).str();
}
125
Konrad Rudolph

同様の質問については this answer をご覧ください。

ご参考までに、CTTのソリューションを再投稿しています。

string readFile2(const string &fileName)
{
    ifstream ifs(fileName.c_str(), ios::in | ios::binary | ios::ate);

    ifstream::pos_type fileSize = ifs.tellg();
    ifs.seekg(0, ios::beg);

    vector<char> bytes(fileSize);
    ifs.read(bytes.data(), fileSize);

    return string(bytes.data(), fileSize);
}

このソリューションは、Moby Dick(1.3M)のテキストに対して平均100回実行した場合、ここに示した他の回答よりも実行時間が約20%高速になりました。ポータブルC++ソリューションとしては悪くない、ファイルをmmapした結果を見てみたい;)

48
paxos1977

最短バリアント:Live On Colir

std::string str(std::istreambuf_iterator<char>{ifs}, {});

ヘッダー<iterator>が必要です。

このメソッドは、文字列を事前に割り当ててstd::istream::readを使用するよりも遅いという報告がいくつかありました。ただし、最適化を有効にした最新のコンパイラでは、さまざまなメソッドの相対的なパフォーマンスはコンパイラに大きく依存しているように見えますが、これはもはや当てはまらないようです。

43
Konrad Rudolph

つかいます

#include <iostream>
#include <sstream>
#include <fstream>

int main()
{
  std::ifstream input("file.txt");
  std::stringstream sstr;

  while(input >> sstr.rdbuf());

  std::cout << sstr.str() << std::endl;
}

または何か非常に近い。自分で再確認するためのstdlib参照がありません。

はい、私は尋ねられたようにSlurp関数を書いていないことを理解しています。

16
Ben Collins

tellg()を使用して応答に直接コメントするほどの評判はありません。

tellg()はエラー時に-1を返す可能性があることに注意してください。 tellg()の結果を割り当てパラメーターとして渡す場合は、最初に結果を健全性チェックする必要があります。

問題の例:

_...
std::streamsize size = file.tellg();
std::vector<char> buffer(size);
...
_

上記の例では、tellg()でエラーが発生した場合、-1が返されます。符号付き(つまりtellg()の結果)と符号なし(つまり_vector<char>_コンストラクターの引数)間の暗黙的なキャストは、ベクトルが誤ってvery largeを割り当てることになりますバイト数。 (おそらく4294967295バイト、または4GB。)

上記を説明するためにpaxos1977の回答を変更します。

_string readFile2(const string &fileName)
{
    ifstream ifs(fileName.c_str(), ios::in | ios::binary | ios::ate);

    ifstream::pos_type fileSize = ifs.tellg();
    if (fileSize < 0)                             <--- ADDED
        return std::string();                     <--- ADDED

    ifs.seekg(0, ios::beg);

    vector<char> bytes(fileSize);
    ifs.read(&bytes[0], fileSize);

    return string(&bytes[0], fileSize);
}
_
12
Rick Ramstetter

C++ 17(std :: filesystem)がある場合は、次の方法もあります(seekgおよびtellgの代わりに_std::filesystem::file_size_を使用してファイルのサイズを取得します)。

_#include <filesystem>
#include <fstream>
#include <string>

namespace fs = std::filesystem;

std::string readFile(fs::path path)
{
    // Open the stream to 'lock' the file.
    std::ifstream f{ path };

    // Obtain the size of the file.
    const auto sz = fs::file_size(path);

    // Create a buffer.
    std::string result(sz, ' ');

    // Read the whole file into the buffer.
    f.read(result.data(), sz);

    return result;
}
_

:標準ライブラリがまだC++ 17を完全にサポートしていない場合は、_<experimental/filesystem>_および_std::experimental::filesystem_を使用する必要がある場合があります。 non-const std :: basic_string data をサポートしていない場合は、result.data()を_&result[0]_に置き換える必要があるかもしれません。

5
Gabriel M

このソリューションは、エラーチェックをrdbuf()ベースのメソッドに追加します。

_std::string file_to_string(const std::string& file_name)
{
    std::ifstream file_stream{file_name};

    if (file_stream.fail())
    {
        // Error opening file.
    }

    std::ostringstream str_stream{};
    file_stream >> str_stream.rdbuf();  // NOT str_stream << file_stream.rdbuf()

    if (file_stream.fail() && !file_stream.eof())
    {
        // Error reading file.
    }

    return str_stream.str();
}
_

元のメソッドにエラーチェックを追加することは、期待するほど簡単ではないため、この答えを追加しています。元のメソッドは、stringstreamの挿入演算子(str_stream << file_stream.rdbuf())を使用します。問題は、文字が挿入されていないときに文字列のフェイルビットが設定されることです。エラーが原因であるか、ファイルが空であることが原因である可能性があります。フェイルビットを検査して障害をチェックすると、空のファイルを読み取るときに誤検知が発生します。ファイルが空であるため、文字を挿入する正当な失敗と文字を挿入する「失敗」を明確にするにはどうすればよいですか?

空のファイルを明示的にチェックすることを考えるかもしれませんが、それはより多くのコードと関連するエラーチェックです。

挿入操作では(ostringstreamまたはifstreamで)eofbitが設定されないため、失敗条件str_stream.fail() && !str_stream.eof()の確認は機能しません。

そのため、解決策は操作を変更することです。 ostringstreamの挿入演算子(<<)を使用する代わりに、eofbitを設定するifstreamの抽出演算子(>>)を使用します。次に、障害状態file_stream.fail() && !file_stream.eof()を確認します。

重要なのは、file_stream >> str_stream.rdbuf()が正当な失敗に遭遇したとき、eofbitを設定するべきではないということです(仕様の私の理解によると)。つまり、正当な障害を検出するには上記のチェックで十分です。

3
tgnottingham

Std :: stringのconst char *バッファーには書き込まないでください。今までにない!そうすることは大きな間違いです。

Std :: string内の文字列全体の領域をReserve()し、ファイルから適切なサイズのチャンクをバッファに読み込み、append()します。チャンクの大きさは、入力ファイルのサイズによって異なります。他のすべてのポータブルおよびSTL準拠のメカニズムが同じことを行うと確信しています(まだきれいに見えるかもしれません)。

3
Thorsten79

このようなものはそれほど悪くないはずです:

void Slurp(std::string& data, const std::string& filename, bool is_binary)
{
    std::ios_base::openmode openmode = ios::ate | ios::in;
    if (is_binary)
        openmode |= ios::binary;
    ifstream file(filename.c_str(), openmode);
    data.clear();
    data.reserve(file.tellg());
    file.seekg(0, ios::beg);
    data.append(istreambuf_iterator<char>(file.rdbuf()), 
                istreambuf_iterator<char>());
}

ここでの利点は、予約を最初に行うため、読みながら文字列を大きくする必要がないことです。欠点は、文字ごとに行うことです。よりスマートなバージョンでは、読み取りbuf全体を取得してからアンダーフローを呼び出すことができます。

3
Matt Price

'std :: getline'関数を使用し、区切り文字として 'eof'を指定できます。ただし、結果のコードは少しあいまいです。

std::string data;
std::ifstream in( "test.txt" );
std::getline( in, data, std::string::traits_type::to_char_type( 
                  std::string::traits_type::eof() ) );
2
Martin Cote