もうよい!std::istreamにはstd::ios::sitbitフラグがセットされるべき
std::istreamから行末まで読みたい場合、最もお手軽に済ませるにはstd::getline()を使うと良い。
動くサンプル:
#include <iostream> #include <iostream> #include <iomanip> #include <fstream> #include <sstream> #include <string> /// ストリームの状態を表示します。(デバッグ用) static void dump_stream_state(std::ios& stream) { using namespace std; ios_base::iostate flags = stream.rdstate(); bool eof = stream.eof(); bool bad = stream.bad(); bool good = stream.good(); bool fail = stream.fail(); cerr << "falgs=" << flags << ", good=" << good << ", bad=" << bad << ", fail=" << fail << ", eof=" << eof << endl; } // 行読み込み方法1: std::getline()使用 bool getline1(std::istream& stream, std::string& line) { bool failed = !std::getline(stream, line); dump_stream_state(stream); return !failed; } int main(int argc, char* argv[]) { using namespace std; std::istringstream ist1("Hello World! This is a pen.\n"); // 改行有り std::istringstream ist2("The rain in Spain stays mainly in the plain."); // 改行無し std::string line; bool suc; cerr << boolalpha; cerr << "******* from istringstream (with LF) *******" << endl; suc = getline1(ist1, line); cerr << "line='" << line << "', result=" << suc << endl; cerr << "******* from cin (with LF) *******" << endl; suc = getline1(cin, line); cerr << "line='" << line << "', result=" << suc << endl; cerr << "******* from istringstream (without LF) *******" << endl; suc = getline1(ist2, line); cerr << "line='" << line << "', result=" << suc << endl; cerr << "******* from cin (without LF) *******" << endl; suc = getline1(cin, line); cerr << "line='" << line << "', result=" << suc << endl; return 0; }
上記サンプルプログラムの標準入力に次のファイル内容をリダイレクトして実行してみる。
Hello World! This is a pen. The rain in Spain stays mainly in the plain.
ただし2行目は、末尾の改行を無しとする。すると結果は次のようになる。
******* from istringstream (with LF) ******* falgs=0, good=true, bad=false, fail=false, eof=false line='Hello World! This is a pen.', result=true ******* from cin (with LF) ******* falgs=0, good=true, bad=false, fail=false, eof=false line='Hello World! This is a pen.', result=true ******* from istringstream (without LF) ******* falgs=1, good=false, bad=false, fail=false, eof=true line='Hello World! This is a pen.', result=true ******* from cin (without LF) ******* falgs=1, good=false, bad=false, fail=false, eof=true line='Hello World! This is a pen.', result=true
resultはture(成功)で、変数lineには行の内容が確かに入っている。
おk 計画通り
しかしstd::getline(stream, s)は、ストリームに改行が現れるまで際限なくsに読もうとするから、悪意のある入力によってハングアップさせられる危険性がある。そこで受け付ける行の長さに制限を設けよう。たとえば511文字まで受け付けるものとして、上記サンプルのgetline1()を次のように書き直せば良い。
// 行読み込み方法2: istream::getline()使用; バッファサイズ大 bool getline1(std::istream& stream, std::string& line) { const int bufsz = 512; char buf[bufsz]; bool failed = !stream.getline(buf, bufsz); dump_stream_state(stream); line = std::string(buf); return !failed; }
結果。
******* from istringstream (with LF) ******* falgs=0, good=true, bad=false, fail=false, eof=false line='Hello World! This is a pen.', result=true ******* from cin (with LF) ******* falgs=0, good=true, bad=false, fail=false, eof=false line='Hello World! This is a pen.', result=true ******* from istringstream (without LF) ******* falgs=1, good=false, bad=false, fail=false, eof=true line='The rain in Spain stays mainly in the plain.', result=true ******* from cin (without LF) ******* falgs=1, good=false, bad=false, fail=false, eof=true line='The rain in Spain stays mainly in the plain.', result=true
まあおk
書き換え前のgetline1()と同じ結果になた!
しかし、正常な行の平均文字数が十数文字程度だとしたら、1行のために512文字分ものバッファを無条件に確保するのでは大名プログラマと後ろ指を指されてしまう。もっとバッファサイズを小さくして、行が長かったら分割して読み込むことにしよう。
// 行読み込み方法3: istream::getline()使用; バッファサイズ小 bool getline1(std::istream& stream, std::string& line) { const int bufsz = 4; // bufsz < 1行の長さ char buf[bufsz]; line = ""; while (stream.getline(buf, bufsz)) { dump_stream_state(stream); line += std::string(buf); } dump_stream_state(stream); return !stream.fail(); }
結果。
******* from istringstream (with LF) ******* falgs=2, good=false, bad=false, fail=true, eof=false line='', result=false ******* from cin (with LF) ******* falgs=2, good=false, bad=false, fail=true, eof=false line='', result=false ******* from istringstream (without LF) ******* falgs=2, good=false, bad=false, fail=true, eof=false line='', result=false ******* from cin (without LF) ******* falgs=2, good=false, bad=false, fail=true, eof=false line='', result=false
ニギャー; ことごとく失敗した*1…orz
どうもstd::istream::getline()は、行末を発見する前に書き込み先バッファが尽きた場合、std::ios::failbitフラグを立ててしまうっぽい
正しいコードは次のとおりである…
// 行読み込み方法4: istream::getline()使用; バッファサイズ小、対策版 bool getline1(std::istream& stream, std::string& line) { const int bufsz = 16; // bufsz < 1行の長さ char buf[bufsz]; line = ""; for (;;) { bool failed = !stream.getline(buf, bufsz); dump_stream_state(stream); if (failed) { line += std::string(buf); if (stream.bad()) return false; // I/Oエラーが生じた場合 stream.clear(); } else { line += std::string(buf); break; } } return true; }
…かどうかは保証の限りではないが(マテ;
これでとりあえずこれで次のように正しく動く↓
******* from istringstream (with LF) ******* falgs=2, good=false, bad=false, fail=true, eof=false falgs=0, good=true, bad=false, fail=false, eof=false line='Hello World! This is a pen.', result=true ******* from cin (with LF) ******* falgs=2, good=false, bad=false, fail=true, eof=false falgs=0, good=true, bad=false, fail=false, eof=false line='Hello World! This is a pen.', result=true ******* from istringstream (without LF) ******* falgs=2, good=false, bad=false, fail=true, eof=false falgs=2, good=false, bad=false, fail=true, eof=false falgs=1, good=false, bad=false, fail=false, eof=true line='The rain in Spain stays mainly in the plain.', result=true ******* from cin (without LF) ******* falgs=2, good=false, bad=false, fail=true, eof=false falgs=2, good=false, bad=false, fail=true, eof=false falgs=1, good=false, bad=false, fail=false, eof=true line='The rain in Spain stays mainly in the plain.', result=true
しかし「たかが」バッファオーバー対策(純然たるオンメモリ操作)のために複雑な条件分岐が必要となりline+=string(buf)を2つ書かされたり*2、あまつさえストリームのエラー回復の呪文であるところのstd::ios::clear()まで動員する羽目になったりとうんこ臭がぷんぷんするorz
まあ動くのなら我慢しよう、、しかしstd::cinをファイルからリダイレクトするのではなくて、コンソールからにした場合、かなり味わい深い挙動を見ることができる:
******* from istringstream (with LF) ******* falgs=2, good=false, bad=false, fail=true, eof=false falgs=0, good=true, bad=false, fail=false, eof=false line='Hello World! This is a pen.', result=true ******* from cin (with LF) ******* The rain in Spain stays mainly in the plain. falgs=2, good=false, bad=false, fail=true, eof=false falgs=2, good=false, bad=false, fail=true, eof=false falgs=0, good=true, bad=false, fail=false, eof=false line='Hello World! This is a pen.', result=true ******* from istringstream (without LF) ******* falgs=2, good=false, bad=false, fail=true, eof=false falgs=2, good=false, bad=false, fail=true, eof=false falgs=1, good=false, bad=false, fail=false, eof=true line='The rain in Spain stays mainly in the plain.', result=true ******* from cin (without LF) ******* The rain in Spain stays mainly in the plain.^Z falgs=2, good=false, bad=false, fail=true, eof=false falgs=2, good=false, bad=false, fail=true, eof=false falgs=0, good=true, bad=false, fail=false, eof=false line='The rain in Spain stays mainly in the plain.?', result=true
これは、上述の入力ファイル内容("Hello World! ...")をコンソールから手入力し、2行目の末尾において[Ctrl+Z]、引き続き[RETURN]を押した。
最後の行の「?」には'\x0a'が入っている。
こうなる理由はもはや現代科学では解明できないのではないか*3