挿入と抽出は、入出力ストリームの枠組とはまだ完全には適応していません。組み込み型の挿入子と抽出子は、書式設定フラグで制御することができます。 これらは、Rogue Wave の演算子では完全に無視される部分です。Rogue Wave の演算子は、挿入時にはフィールド幅を考慮しません。また抽出時には空白をスキップします。
また、エラー通報も無視します。たとえば、抽出された日付が 2 月 31 日の場合、原因がはっきりしないまま基本バッファ外部デバイスにアクセスできないために挿入時に失敗した場合、ファセットから例外が送出された場合などです。こういったことを防ぐには、ストリームの個々の状態に状態ビットを設定し、例外マスクの指示に従って例外を送出、再送出します。
ここで、もっと基本的な、挿入子と抽出子は何をするのかということを説明します。
書式設定フラグに関して、挿入子と抽出子は次のように機能します。
状態ビットに関して、挿入子と抽出子は次のように機能します。
例外マスクに関して, 挿入子と抽出子は次のように機能します。
ロケールに関して、挿入子と抽出子は次のように機能します。
ストリームバッファに関して、挿入子と抽出子は次のように機能します。
注: 監視オブジェクトを挿入子や抽出子に作成した後は、ストリームの入出力関数を呼び出さないでください。代わりにストリームバッファの関数を使用してください。
これまで構築してきた例に戻って、クラス date の抽出子と挿入子に手を加えてみます。次に示すのは改善後の抽出子です。
template<class charT, class Traits> basic_istream<charT, Traits>& operator >> (basic_istream<charT, Traits >& is, date& dat) { ios_base::iostate err = 0; //1 try { //2 typename basic_istream<charT, Traits>::sentry ipfx(is); //3 if(ipfx) //4 { use_facet<time_get<charT,Traits> >(is.getloc()) .get_date(is, istreambuf_iterator<charT,Traits>() ,is, err, &dat.tm_date); //5 if (!dat) err |= ios_base::failbit; //6 } } // 実行 catch(...) //7 { bool flag = FALSE; try { is.setstate(ios_base::failbit); } //8 catch( ios_base::failure ) { flag= TRUE; } //9 if ( flag ) throw; //10 } if ( err ) is.setstate(err); /11 return is; }
//1 | 変数 err は、エラーの発生を追跡します。この例では、変数 err は time_get ファセットに渡り、ここで個々の状態ビットが設定されます。 |
//2 | 実際の例外の送出前に各エラー状態が正しく設定されるように、抽出子や挿入子内の演算は、すべて try ブロック内に組み込みます。 |
//3 | 先頭の空白のスキップなど、準備段階のすべての処理を行う監視オブジェクトをここで定義します。 |
//4 | 準備処理がすべて正しく行われたかどうかを確認します。クラス sentry には bool への変換機能があり、これで検査を行います。 |
//5 | これは、ストリームのロケールの構文解析ファセットに対する呼び出しです。抽出子の基本バージョンのものと同じです。 |
//6 | 日付が意味論的に有効かどうか、たとえば 2 月 30 日といった誤った日付がないかどうかを調べるための機能が、日付クラスにあるものとします。無効な日付が抽出されると、エラーになって、failbit が設定されるようにします。 |
//7 | この場合、ストリームの setstate() の関数で failbit を設定しないでください。ストリームの例外マスクでオンに設定すると、setstate() でも例外が送出されるためです。この時点の例外の送出は必要ないので、 状態変数 err に failbit を追加します。 |
//8 | ここで、これまでに送出されたすべての例外を捕獲します。これによって、例外で抽出子が停止する前にストリームのエラー状態を設定し、元の例外を送出します。 |
//9 | 最後は、ストリームの setstate() 関数でストリームのエラー状態を設定します。この呼び出しがあると、ストリームの例外マスクに従って ios_base::failure 例外が送出されます。 |
//10 | ios_base::failure 例外を捕獲するのは、 元の例外を送出するためです。 |
//11 | 元の例外を再送出します。これまでに例外が発生していない場合は、ストリームの setstate() 関数でストリームのエラー状態を設定します。 |
挿入子も同じパターンで実装します。
template<class charT, class Traits> basic_ostream<charT, Traits>& operator << (basic_ostream<charT, Traits >& os, const date& dat) { ios_base::iostate err = 0; try { typename basic_ostream<charT, Traits>::sentry opfx(os); if(opfx) { char patt[3] = "%x"; charT fmt[3]; use_facet<ctype<charT> >(os.getloc()) .widen(patt,patt+2,fmt); //1 if ( use_facet<time_put<charT,ostreambuf_反復子<charT,Traits> > > (os.getloc()) .put(os,os,os.fill(),&dat.tm_date,fmt,(fmt+2)) //2 .failed() //3 ) err = ios_base::badbit; //4 os.width(0); //5 } } //実行 catch(...) { bool flag = FALSE; try { os.setstate(ios_base::failbit); } catch( ios_base::failure ) { flag= TRUE; } if ( flag ) throw; } if ( err ) os.setstate(err); return os; }
挿入子と抽出子には、大きな違いはありません。
//1 | ロケールの time_put ファセットにある put() 関数を使用します。融通性があり、1 つだけでなく複数の書式指示子を指定することができます。書式シーケンス指示子をおさめた文字配列を宣言し、必要であればワイド文字に拡張します。 |
//2 | ここで書式指示子を time_put ファセットの put() 関数に与えます。 |
//3 | put() 関数は、生成された最後の文字の直後を示す反復子を返します。反復子 failed() 関数を呼び出して、先の出力が正しいかどうかを確認します。 |
//4 | 出力が失敗するのは、おそらくストリームが壊れている場合なので、badbit を設定します。 |
//5 | ここでフィールド幅をリセットします。ファセットの put() 関数でストリームの書式設定を使用し、各フィールド幅にもとづいて出力を調節するためです。使用後はフィールド幅をリセットするのが原則です。 |
挿入子や抽出子の実装は、なぜ複雑に見えるのでしょう。なぜ最初の単純な方法ではいけないのでしょうか。
まず、実際には、この実装はあまり複雑ではありません (このことについては次の節で説明します)。次に、ほとんどの組み込み型のデータメンバーがユーザー定義型で構成され、実行時の効率がそれほど重要でない場合には、最初のアプローチにおける単純な抽出子と挿入子で十分だということも留意しておいてください。
ただし、入出力演算における実行時の効率性を重視する場合は、ストリームバッファに直接アクセスすることを推奨します。この場合は、高速の低レベルサービスを使用しますが、低レベルサービスでは行わない書式制御やエラー処理などを追加して行う必要があります。このマニュアルで取り上げる例では、最適なパフォーマンスを目指しています。ファセットが直接ストリームバッファにアクセスするため、日付のロケール依存の構文解析と書式設定の抽出子と挿入子は非常に効率性が高くなっています。いずれにしても、これまでに紹介してきたパターンを使用してください。
Copyright (c) 1998, Rogue Wave Software, Inc.
このマニュアルに関する誤りのご指摘やご質問は、電子メールにてお送りください。
OEM リリース, 1998 年 6 月