コード例に戻ります。新しいストリームクラスを派生によって作成します。
新しいストリーム型 odatstream を派生で作成します。ここには、日付の書式設定の指定を設定するためのメンバー関数 fmt() と共に、日付の書式設定文字列を格納するためのデータメンバー fmt_ があります。
template <class charT, class Traits=char_traits<charT> > class odatstream : public basic_ostream <charT,Traits> { public: odatstream(basic_ostream<charT,Traits>& ostr, const char* fmt = "%x") //1 : basic_ostream<charT,Traits>(ostr.rdbuf()) { fmt_=new charT[strlen(fmt)]; use_facet<ctype<charT> >(ostr.getloc()). widen(fmt, fmt+strlen(fmt), fmt_); //2 } basic_ostream<charT,Traits>& fmt(const char* f) //3 { delete[] fmt_; fmt_=new charT[strlen(f)]; use_facet<ctype<charT> >(os.getloc()). widen(f, f+strlen(f), fmt_); return *this; } charT const* fmt() const //4 { charT * p = new charT[Traits::length(fmt_)]; Traits::copy(p,fmt_,Traits::length(fmt_)); return p; } ~odatstream() //5 { delete[] fmt_; } private: charT* fmt_; //6 template <class charT, class Traits> //7 friend basic_ostream<charT, Traits> & operator << (basic_ostream<charT, Traits >& os, const date& dat); };
//1 | 日付の出力ストリームでは、既存の出力ストリームのストリームバッファを流用し、2
つのストリームで同じストリームバッファを共有します。 コンストラクタにはオプションの引数である、日付の書式設定文字列が必要です。これは、常に 1 バイト文字のシーケンスです。 |
//2 | 書式設定文字列は、ストリームの文字型 charT に展開または変換されます。書式設定文字列は、ストリームのロケールの時間ファセットに必要であり、このファセットが charT 型の文字配列を要求するためです。 |
//3 | 関数 fmt() のこのバージョンでは、書式設定文字列を設定することができます。 |
//4 | 関数 fmt() のこのバージョンでは、現在の書式設定文字列の設定を返します。 |
//5 | date ストリームクラスには、書式設定文字列を削除するデストラクタが必要です。 |
//6 | 日付の書式設定の指定までのポインタは、非公開データメンバー fmt_ として保存されます。 |
//7 | 日付挿入子は、日付の書式設定の指定にアクセスする必要があります。そのため、クラス odatstream のフレンドにします。 |
ここでの目的は、date オブジェクトを各種の出力ストリームに挿入することです。出力ストリームが odatstream 型の日付出力ストリームの場合、その機能を利用して、日付出力の書式を設定するための別の情報も組み込んでみます。
date オブジェクトの挿入子が、種類の異なる出力ストリームごとに異なる実装が可能な、さまざまな出力ストリームクラスの仮想メンバー関数であれば、理想的です。 たとえば、日付オブジェクトを odatstream に挿入すると、利用できる日付の書式設定文字列で書式設定が行われます。任意の出力ストリームに挿入すると、デフォルトの書式設定が行われます。ただし、既存の出力ストリームクラスは、変更できないライブラリの一部であるため変更はできません。
この種の問題は動的キャストで処理します。ストリームクラスにはクラス basic_ios から継承された仮想デストラクタがあるため、動的キャストを利用することで、必要な仮想動作を得ることができます。27
次に、日付挿入子の実装方法を示します。
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) { charT* fmt; charT buf[3]; try { //1 odatstream<charT,Traits>* p = dynamic_cast<odatstream<charT,Traits>*>(&os); //2 } catch (bad_cast) //3 { char patt[3] = "%x"; use_facet(os.getloc(), (ctype<charT>*)0).widen(patt,patt+3,buf); } fmt = (p) ? p->fmt_ : buf; //4 if (use_facet<time_put<charT,ostreambuf_iterator<charT,Traits> > >(os.getloc()) .put(os,os,os.fill(),&dat.tm_date,fmt,fmt+Traits::length(fmt)).failed()) err = ios_base::badbit; os.width(0); } } //実行 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 | //2 では動的キャストを行います。動的キャストでは、ミスマッチがあると例外が送出されます。ミスマッチはエラー条件を表すわけではなく、単にデフォルトの書式設定を実行することを表すだけなので、ユーザーに対しては bad_cast 例外は表示しません。このため、潜在的な bad_cast 例外を捕獲します。 |
//2 | これは、ストリームが日付ストリームか他の出力ストリームかを調べるための、動的キャストです。 |
//3 | ミスマッチの場合は、デフォルトの日付の書式設定の指定 "%x" を用意します。 |
//4 | ストリームの型が odatstream ではない場合、catch 句で用意したデフォルトの書式設定の指定を使用します。それ以外は、非公開データメンバー fmt_ から書式設定の指定を取り出します。 |
日付出力ストリームには、書式設定の指定を設定するためのメンバー関数があります。標準ストリームの書式設定関数と同様に、書式を設定するためのマニピュレータを用意します。このマニピュレータは、出力ストリームだけを制御します。そのため、出力ストリームマニピュレータ osmanip には、このマニピュレータに必要な挿入子と共に、マニピュレータの基底クラスを定義する必要があります。それが次のコードです。ここで使用する手法の詳細については、12.3.3 節を参照してください。
template <class Ostream, class Arg> class osmanip { public: typedef Ostream ostream_type; typedef Arg argument_type; osmanip(Ostream& (*pf)(Ostream&, Arg), Arg arg) : pf_(pf) , arg_(arg) { ; } protected: Ostream& (*pf_)(Ostream&, Arg); Arg arg_; friend Ostream& operator<< (Ostream& ostr, const osmanip<Ostream,Arg>& manip); }; template <class Ostream, class Arg> Ostream& operator<< (Ostream& ostr,const osmanip<Ostream,Arg>& manip) { (*manip.pf_)(ostr,manip.arg_); return ostr; }
以上の準備が完了したら、setfmt マニピュレータそのものを実装します。
template <class charT, class Traits> inline basic_ostream<charT,Traits>& sfmt(basic_ostream<charT,Traits>& ostr, const char* f) //1 { try { //2 odatstream<charT,Traits>* p = dynamic_cast<odatstream<charT,Traits>*>(&ostr); } catch (bad_cast) //3 { return ostr; } p->fmt(f); //4 return ostr; } template <class charT,class Traits> inline osmanip<basic_ostream<charT,Traits>,const char*> setfmt(const char* fmt) { return osmanip<basic_ostream<charT,Traits>,const char*>(sfmt,fmt); } //5
//1 | 関数 sfmt() は、setfmt マニピュレータに対応する関数です。これは書式設定の指定を引き取ってストリームに渡します。これは、ストリームが出力ストリームの場合に実行されます。その他のストリームでは処理は行われません。 |
//2 | ストリームの型は、動的キャストで指定します。例外を送出してマニピュレータ呼び出しを行う方法よりも強力な方法なので、潜在的な bad_cast 例外送出になります。 |
//3 | ミスマッチの場合は、処理は行わずに元に戻ります。 |
//4 | ストリームが日付出力ストリームの場合は、書式設定の指定はストリームの fmt() 関数を呼び出して保存します。 |
//5 | マニピュレータ自体は、出力マニピュレータオブジェクトを作成する関数です。 |
17.4.3 節で説明した方法では、動的キャストと例外の処理を利用して日付挿入子と日付の書式設定マニピュレータを実装しています。この手法は洗練されており、 C++ 言語の使用方法にも問題はありませんが、例外の処理を使用するため、実行時にパフォーマンスが多少低下する可能性があります。特に、動的キャスト式とそれによる例外が、ある種の分岐文として使用される場合に顕著です。つまり、「例外的」な事例は、そのほとんどが実際の例外ではないのに、比較的頻繁に発生するためです。
最適なパフォーマンスを目的とする場合、別の方法が考えられます。先の動的キャストを使用する解法の中で、任意出力ストリームである basic_ostream<charT,Traits>& operator<< (basic_ostream <charT,Traits>&、const date&) の日付挿入子を拡張します。これは、出力ストリームの型に応じてさまざまな日付の書式を設定するためです。または、出力ストリーム用の既存の日付挿入子を変更せずに、出力日付ストリームとしてのみ機能する日付挿入子を追加します。その識別形式は、 odatstream<charT,Traits>& operator<< (odatstream<charT,Traits>&, const date&) となります。2 つのマニピュレータ関数 basic_ostream<charT,Traits>& sfmt (basic_ostream<charT,Traits>&, const char*) と odatstream<charT,Traits>& sfmt (odatstream<charT,Traits>&, const char*) を使用し、1 つを任意の出力ストリームに、そして別の 1 つを日付ストリーム専用とします。日付ストリームの各関数では、出力日付ストリームに固有の演算を置き換えます。
この方法では、ほとんどの挿入子のコードが重複するため、保守が困難になりますが、実行時のパフォーマンスを向上させることができます。
Copyright (c) 1998, Rogue Wave Software, Inc.
このマニュアルに関する誤りのご指摘やご質問は、電子メールにてお送りください。
OEM リリース, 1998 年 6 月