「MT-安全」の iostream ライブラリの構成は、「MT-安全ではない」(マルチスレッド環境では使用できない) iostream ライブラリの構成と少し違います。「MT-安全」の iostream ライブラリから公開されているインタフェースは、iostream クラスとその基底クラスすべての公開および限定公開メンバー関数しか参照しないことは他のバージョンと同じですが、クラス階層が異なります。詳しくは、「iostream ライブラリのインタフェースの変更」を参照してください。
本来のコアクラスは、名前に接頭語 unsafe_ が付けられました。表 4-1 に、iostream パッケージのコアクラスを示します。
表 4-1 コアクラス
クラス |
内容 |
---|---|
stream_MT |
MT-安全の基底クラス |
streambuf |
バッファーの基底クラス |
unsafe_ios |
エラー状態やフォーマット状態などの、さまざまなストリームクラスに共通の状態変数を含むクラス |
unsafe_istream |
streambuf から取り出した文字シーケンスのフォーマット付きまたはフォーマットなし変換をサポートするクラス |
unsafe_ostream |
streambuf に格納される文字シーケンスのフォーマット付きまたはフォーマットなし変換をサポートするクラス |
unsafe_iostream |
入出力両方向の操作のために unsafe_istream クラスと unsafe_ostream クラスを結合するクラス |
「MT-安全」の各クラスは、基底クラス stream_MT から派生します。streambuf を除く「MT-安全」クラスもまた、すべて名前が unsafe_ で始まる既存の基底クラスから派生します。次に例を示します。
class streambuf: public stream_MT { ... }; class ios: virtual public unsafe_ios, public stream_MT { ... }; class istream: virtual public ios, public unsafe_istream { ... };
stream_MT クラスは、それぞれの iostream クラスを「MT-安全」にするために必要な相互排他ロック機能を提供します。また動的にロックとロック解除を行う機能も提供し、「MT-安全」特性の動的変更を可能にします。入出力変換とバッファー管理の基本機能は、名前が unsafe_ で始まるクラスにまとめられており、「MT-安全」にするためにライブラリに追加した機能は、その派生クラスに限られます。「MT-安全」の各クラスに入っている公開および限定公開メンバー関数は、名前が unsafe_ で始まる基底クラスと同じです。「MT-安全」のクラスの各メンバー関数はラッパーとして働き、オブジェクトのロック後、名前が unsafe_ で始まる基底クラスの同じ関数を呼び出し、最後にそのオブジェクトのロックを解除します。
streambuf クラスは、名前が unsafe_ で始まるクラスから派生したクラスではありません。streambuf クラスの公開および限定公開メンバー関数は、ロック機能により再入可能になります。_unlocked という接尾辞の付いたロックなしのものも提供されています。
iostream インタフェースには、マルチスレッド環境で使用しても安全で、かつ再入可能な公開関数がいくつか追加されています。各関数の追加引数として、ユーザー定義バッファーを指定します。次に関数の内容を示します。
表 4-2 再入可能な公開関数
関数 |
内容 |
---|---|
char *oct_r (char *buf, int buflen, long num, int width) |
数値を 8 進表示した ASCII 文字列へのポインタを返します。幅がゼロ以外の場合は、フォーマット用のフィールド幅とみなされます。戻り値がユーザー提供バッファーの先頭を指していることは保証されません。 |
char *hex_r (char *buf, int buflen, long num, int width) |
数値を 16 進表示した ASCII 文字列へのポインタを返します。幅がゼロ以外の場合は、フォーマット用のフィールド幅とみなされます。戻り値がユーザー提供バッファーの先頭を指していることは保証されません。 |
char *dec_r (char *buf, int buflen, long num, int width) |
数値を 10 進表示した ASCII 文字列へのポインタを返します。幅がゼロ以外の場合は、フォーマット用のフィールド幅とみなされます。戻り値がユーザー提供バッファーの先頭を指していることは保証されません。 |
char *chr_r (char *buf, int buflen, int chr, int width) |
文字列 chr が含まれた ASCII 文字列へのポインタを返します。幅がゼロ以外の場合は、文字列には width 個のスペースに続いて chr が含まれます。戻り値がユーザー提供バッファーの先頭を指していることは保証されません。 |
char *form_r (char *buf, int buflen, const char *form,...) |
フォーマット文字列 format に続く引数を sprintf でフォーマットした文字列へのポインタを返します。バッファーには、フォーマット済み文字列を入れるための十分な長さが必要です。 |
旧バージョンの libC ライブラリとの互換性を保つために提供されている iostream ライブラリの公開変換ルーチン (oct、hex、dec、chr、form) は、「MT-安全」ではありません。
マルチスレッド環境で実行するために、libC ライブラリの iostream クラスを使用して作成したアプリケーションについては、ソースコードのコンパイルとリンク時に -mt オプションを指定する必要があります。このオプションを指定すると、前処理部には -D_REENTRANT が、リンカーには -lthread が渡されます。
libC および libthread とのリンクには、-lthread ではなく -mt を使用してください。-mt オプションを使用すると、ライブラリの正しい順番でのリンクが保証されます。誤って -lthread オプションを使用すると、そのアプリケーションは正しく動かないことがあります。
iostream クラスを使用するシングルスレッド用のアプリケーションには特別なコンパイラやリンカーオプションは必要ありません。デフォルトで、コンパイラは libC ライブラリとリンクします。
iostream ライブラリのマルチスレッドに対する安全性に制限があるのは、iostream ライブラリで使用しているプログラミング手法の多くが、共有 iostream オブジェクトを使用するマルチスレッド環境では正しく実行できないためです。
「MT-安全」にするには、次の例のように、エラーを起こすような入出力操作を伴う危険領域では、入出力のエラーを調べる必要があります。
#include <iostream.h> #include <rlocks.h> enum iostate { IOok, IOeof, IOfail }; iostate read_number(istream& istr, int& num) { stream_locker sl(istr, stream_locker::lock_now); istr >> num; if (istr.eof()) return IOeof; if (istr.fail()) return IOfail; return IOok; }
この例では、stream_locker オブジェクトである sl のコンストラクタが istream オブジェクトの istr をロックしています。sl のデストラクタは、read_number の終了時に呼ばれ、istr のロックを解除します。
「MT-安全」にするには、最も近いフォーマットなし入出力とそれに続く gcount 呼び出しの間に、iostream オブジェクトを排他的に使用するスレッド内で gcount 関数の呼び出しを行う必要があります。次の例を参照してください。
#include <iostream.h> #include <rlocks.h> void fetch_line(istream& istr, char* line, int& linecount) { stream_locker sl(istr, stream_locker::lock_defer); sl.lock(); // ストリーム istr をロック istr >> line; linecount = istr.gcount(); sl.unlock(); // istr のロック解除 ... }
この例では、stream_locker クラスの lock メンバー関数および unlock メンバー関数がプログラム中で相互排他領域を定義しています。
「MT-安全」にするには、ユーザー定義型に対して定義された入出力操作で特定の操作順序を持つものは、ロックして危険領域を定義する必要があります。次の例を参照してください。
#include <rlocks.h> #include <iostream.h> class mystream: public istream { // その他の定義... int getRecord(char* name, int& id, float& gpa); } int mystream::getRecord(char* name, int& id, float& gpa) { stream_locker sl(this, stream_locker::lock_now); *this >> name; *this >> id; *this >> gpa; return this->fail() == 0; }
現バージョンの libC ライブラリの「MT-安全」のクラスを使用すると、シングルスレッドのアプリケーションの場合でもパフォーマンスのオーバーヘッドが起こります。 libC ライブラリの unsafe_ クラスを使用すると、オーバーヘッドはなくなります。
次の例のように、スコープ決定演算子を使用して unsafe_ 基底クラスのメンバー関数を実行することができます。
cout.unsafe_ostream::put('4');
cin.unsafe_istream::read(buf, len);
マルチスレッド対応のアプリケーションでは、unsafe_ クラスの使用は危険です。
次の例のように、unsafe_ クラスを使用する代わりに、cout オブジェクトと cin オブジェクトを「MT-安全ではない」として、通常の操作を使用することもできます。この場合、パフォーマンスはわずかに低下します。
#include <iostream.h> cout.set_safe_flag(stream_MT::unsafe_object); //「MT-安全ではない」に設定 cin.set_safe_flag(stream_MT::unsafe_object); //「MT-安全ではない」に設定 cout.put('4'); cin.read(buf, len);
iostream オブジェクトが「MT-安全」である場合は、相互排他ロック機能が提供されてオブジェクトのメンバー変数が保護されます。このロック機能のため、シングルスレッド環境でのみ実行されるアプリケーションの場合は不要なオーバーヘッドがかかります。 次の例のように、iostream オブジェクトの「MT-安全」と「MT-安全ではない」を動的に切り換えて、パフォーマンスを向上させることができます。
fs.set_safe_flag(stream_MT::unsafe_object);//「MT-安全ではない」に設定 ... さまざまな入出力操作を実行
iostream が複数スレッドに共有されない場合、たとえばスレッドが 1 つしかないプログラムや、各 iostream がそれぞれのスレッドに対して非公開なプログラムの場合は、 「MT-安全ではない」のストリームを使用しても問題ありません。
また次の例のように、プログラム内で明示的に同期を取らせる場合は、iostream が複数スレッドに共有される環境で「MT-安全ではない」の iostream を使用しても問題ありません。
generic_lock() ; fs.set_safe_flag(stream_MT::unsafe_object) ; ... さまざまな入出力操作を実行 generic_unlock() ;
ここで、generic_lock と generic_unlock の関数は、相互排他、セマフォ、読み取り/書き込みのような単純型を使用した同期機構を提供するものであれば何でもかまいません。
このような目的で使用するには、libC ライブラリで提供されている stream_locker クラスをお勧めします。
詳細は、「オブジェクトのロック」を参照してください。