PPPUC++#12

10 Input and Output Streams

"Science is what we have learned about how to keep from fooling ourselves." 尊敬するファインマン博士のお言葉。我々の認識はしばしば自身を騙しますが、それを認識できるのが不思議です。We're a Strange Loopですね。
この10,11章にてC++標準関数の I/O Streamをやります。10章は基礎。ファイルとかエラー処理とかフォーマットとかUDTでのI/O。

10.1 Input and Output

  • データなしには始まらない
  • データ元が非常に限られる用途もあるけれど、普通は汎用的にすべき
    • 抽象化して分離すべき
    • そうでないと、ディスプレイやハードディスクが新しくなるたびに、コードを書き直す必要が出てきて大変
      • それを実現しているのはどっちかというとOSな気もしますが、と思ったらそう続いてた
  • 近年のOSでは、(I/Oライブラリ [デバイスドライバの詳細 <ハードウェアの詳細>])というように尖った詳細を柔らかい層で包んで、異なるデバイスを可能な限り「同じように」見せかけてます。
      • 正確には ( アプリケーションレベル OBJECT ( 高レベルI/Oライブラリ ( 低レベルI/Oライブラリ ( システムコール ( デバイスドライバ ( ハードウェア ) ) ) ) ) )てな感じかな
  • 大抵の場合、ドライバとかデバイスとかの「下賎なもの」たちは、ライブラリや言語の機構といった手下の皆様のがんばりのお陰で普通のプログラマには見えません
      • 感謝しましょー
  • こうなると入出力は全て単なるバイト(文字)ストリームにしか見えません。我々がしなきゃいけないのは、
    1. I/Oストリームを目的のデバイスにセットアップ
    2. そのストリームから読み/書きする
  • こんだけ。なんと簡単
  • 入出力にはいろんなタイプがあります
    1. ファイル、ネットワーク、記憶装置、ディスプレイといったデータアイテム
    2. キーボードによるユーザとの対話
    3. GUIによるユーザとの対話
      • という分類をしてみたものの、明確じゃないよねー、とかお茶を濁したりなど。やっぱり(ry
    • 確かにHTTPだからsocket抽象で1番目!と思っていても、それキーボード経由のユーザからのsubmitだったり、マウスクリック情報だったりする訳で
  • まあしかし、今回の目的のおいては最初の2つは標準ライブラリで扱えて、すなわちどんな環境でもオKのもの、3番目は直接は扱えないもの、ということでどうか
  • この章はこれまでも触ってきたiostreamライブラリに注力。華やかなGUIは12,16章にてやりますのでよしなに

10.2 The I/O stream model

  • istream -> input, ostream -> output 。まあそのまんま
  • ここまでで使ってきたのは istream -> cin , ostream -> cout,
      • (さらにcerr。ぬけてっぞ)
  • ostreamは・・・
    1. 様々な型を文字列へ変換
    2. それらをどこかに送る(コンソールとかファイルとかメモリとか別のコンピュータとか)
    • 図にするとはこんな感じ(描画にはdotを使用)

  • ここで Bufferはostreamが使っている内部データ構造
    • 巨大なデータセットを使う場合とかに大きく効いてくる
  • 次 istream
    • 単純に逆方向

  • outよりもBufferの効果が見えやすい、、、というよりENTERを押すまで先に進まない&BSで戻れる、とか
  • なんだかんだ言って「出力」は人間が理解しやすいように出力する、と言う用途が多い
    • というわけでostreamにもそのためのフォーマット等の機能が集約されている
  • 同様に入力と言うのも人間が理解しやすいようになっている事が多い
      • 特にネットの発達以降
    • というわけで(ry
    • 11.2にてフォーマットについて。テキスト情報じゃない入力について 11.3.2にて
  • 入力で最も複雑なのはエラー処理。
  • まずはファイルから

10.3 File

  • 我々の持ってる情報量は大抵はコンピュータのメモリを凌駕します
    • なのでディスクに蓄えてしまう。不揮発性と言うおまけももれなくついてきます。
  • 一番単純なレベルでは「ファイル」は単なるバイトの並び
    • で、その単なる「バイト」がどんな意味を持つか、という決まり、ファイルフォーマットを科して利用します
    • フォーマットと言うのは、言語でいうと型のようなもの。型がメモリ上での意味を規定していて、ディスク上ではフォーマットが意味を定義している
  • ostreamはメモリ上のオブジェクトをバイトストリームに変換し、ディスク転送するのですね
  • istreamは逆ですね
  • ここまで、ostreamの出口は磁気ディスクと決めてかかってたけど、そうとは限らず。しかし file streamの抽象のお陰で気にしなくてもオK。

うーん例によってクドい

  • 名前を指定してファイルを「開く」「閉じる」という手順が増えるけれども、基本的にはあとはこれまでやってきた cout , cinと同じ。ご安心を。
  • ファイル固有の話については 11.3.3にて

10.4 Opening a file

  • まずは「開く」から
  • これはiでもoでも必要な、streamと実ファイルを紐付けする作業
  • ファイル用のストリームは ifstream, ofstreamとfがつきます
  • なんと *fstream()の引数(ファイル名)はstringではなく C文字列
    • これこそ最大の「それ変えろよ」だと思うのですが、午後のひととき皆様いかがお思いでしょうか?
  • というわけで、stringからファイル名を得たい場合は string.c_str() にてC文字列を取得する必要があります
    • 多くのシステムインターフェイスでC文字列はまだまだ必要なのです
      • しかし*stream(const string&)の皮かぶせるだけで良いと思うんだけど、ダメなのかな・・?
  string name;
  cin >> name;
  ifstream ist(name.c_str());  // ist is an input stream for the file named name
  if (!ist) error("can't open input file ", name);
  • if()で boolとして fistreamオブジェクトを見てみると正常に「開く」事ができたかわかります
    • というか streamオブジェクトの現在の健康状態がわかります、エラーが出ると false
      operator bool() const
      { return _M_ok; }
  • あとはなんであれ(定義されていれば) >>でオブジェクトを指定するだけでそこに入力されます
  • outputも同様。略
  • closeは?って。実はstreamオブジェクトがスコープから出ると、自動的にデストラクタでクローズされます
  • closeすると flushが起きます。つまりバッファに残っている文字をディスクに吐き出します
  • なるべく計算始める前の早い段階でオープンしておくべき。沢山計算した後で「ああ、書くとこない。紙、紙!」となると目も当てられない
      • 僕は逆に考えてました。計算は安く、ファイルオープンは重いし、もし異常終了したら開きっぱなしになる危険(普通はOSが屍を拾ってくれますが)。とかから、なるべくギリギリまでオープンしない癖が。。。
      • はずかしー
  • open, closeを明示的に使うのもオKなんだけど、そうすると自分でその管理をする必要が出てきます。
    • *streamを作る事で暗黙的にファイルをopenし、それのスコープがキレたところで自動的にcloseさせた方が楽だし間違いないです。
  • ただしこれはあくまで理想。現実はそう簡単には行かない
    • しかしありがたい事に、大抵は同じファイルをcloseする前に openしようとするとOSにトラップされる
      • た、だ、し、オープンした後にチェックしなければ意味ないので気をつけましょう
  • open(), close()を明示的にしなければ行けない状況って?
    • ファイルに接続している期間がスコープとぴったり合うとは限らない
    • 例えば、他のスコープにstreamオブジェクトを渡す場合などは明示的にどこかでクローズしてもらわざるを得ない
  • さて、まだまだ沢山知る事はあるけれど、とりあえず使うことは可能になりました。
  • ファイルリードっていうのはデバッグにも有用です。いちいちて入力しなくて良くなります。

10.5 Reading and writing a file

  • 実践編
  • 例えば、ということで気象台の温度計測ファイルを読み込む事を考える

0 60.7
1 60.6
2 60.3
3 59.22
:

  • 時刻 温度計測値の順に一行に並ぶタイプ。時刻は 24時間制で温度は華氏(さすがアメリカ人)
    • ヘッダも無ければ単位情報も無い。句読点とか括弧の類も無いし、終端文字も無い、という単純なものです
  • Readingクラスを作る、と言う方針
struct Reading {
  int hour;
  double temperature;
  Reading(int h, double t) : hour(h), temperature(t) {}
};
  • で、どうするのかと思ったらおもむろに cin >> hour >> temperatureしてコンストラクタ呼ぶのかw
  • 問題点として、エラーが発見できない、とかいろいろ挙げられそう
  • 出力は入力よりも一般的には簡単
    • cout << 出す値 するだけだものね・・・
  • で、完成品が提示されてる
    • 微妙なんだけど 僕はどうも リードループの変数をそのループの外に持ち出すのがいやで for (<>;;)にいれ込んでしまう。Mr.Sはそうじゃないみたいね。

10.6 I/O error handling

  • エラーです
  • どんなエラー?
    • ヒューマンエラー:命令の勘違いとか、TYPOとか、ネコのキーボード蹂躙を許す(え!?)とか
    • ファイルフォーマットエラー:想定が間違ってたとか。。。ってヒューマンエラーじゃん
    • ・・・・いや限りない可能性があるよ!
      • そりゃそうだよ
  • しかし istreamは世界を単純化し過ぎている! 4つしかない!
    1. good() .. 成功!
    2. eof() .. 入力終わり!
    3. fail() .. 何か予測不能なことが起きた!
    4. bad() .. 何か予測不能でヤバい事が起きた!
      • 3番目と4番目の差わいったいw
    • 正確には定義されていないらしい上に意見が割れているらしい。さいあく
  • しかしまあ基本は単純
    • 単なるフォーマットエラーなら fail()で、これはユーザがリカバリ可能なもの
    • バッドディスクだとかもうどうしようもねー、すてるしかねー、と言うときに bad()
  • と言う感じ。
  • というわけで一般的には bad()がでたらerror()でベイルアウト。 fail()がでたら確認後 clear()でフラグをリセット、eof()なら正常終了、という当たり前の結論に
  • あとは例。