C++の小ネタ。マルチスレッド/マルチプロセスで出力が溢れかえるのをなんとかしたかったので作りました。
print文デバッグは実行中の変数の状態を知るのに手軽な方法としてしばしば使いますが、マルチスレッド/マルチプロセスで動く関数をデバッグしていると、スレッド/プロセスの数だけ一気に出力されてしまって大変見づらくなります。
マルチスレッド化の最中など全スレッドの状態を見たい場合もあるのですが、そうでないときはいろいろと混ぜながら出力されてしまうので何がなんだかわからなくなります。
こんなときはif文を使って一応の解決ができます。どこかに出力フラグisOutのようなものがあったとして、
#include void someThread(bool isOut){ if(isOut) std::cout << "hogehoge" << std::endl; }
みたいに書けば一応解決します。しかしいちいちif文まで書くのはどうも面倒だし、そもそも、C++のストリーム関数はいくらでも好きなものを定義できるのが利点だったはずです。
そんなときのために出力するかどうかを選べるようになっていると便利ですね。
というわけで作りました。
まずは何も出力しないストリームを作ります。/dev/nullみたいなものです。
#include #include class null_streambufClass : public std::streambuf{ virtual int overflow(char c){ return traits_type::not_eof(c); //return success } }; class onullstream : public std::ostream{ null_streambufClass null_streambuf; public: onullstream() : std::ostream(&null_streambuf){} }; onullstream nullout;
C++のストリーム(ここではstd::ostream)は、それ自身に実装を書いて新しいクラスを作るように設計されていません。内部に持つstreambufクラスを定義/継承することで振る舞いを変えるようになっています。
そこで今回は何もしないstreambufクラスを用意します。実装といってもchar型の値を受け取る(そして何もしない)overflow関数だけです。
次にストリームクラスを作ります。継承元のstd::ostreamのコンストラクタにstreambufクラスのポインタを渡すことで作られます。せっかくなのでstd::coutのようにグローバル領域にインスタンスをひとつ作っておきました。
これで何もしない出力ストリームの準備ができました。あとはstd::coutとnulloutを適当に切り替えられればいいことになります。
簡単な使い方は参照を返す関数を書いておくことです。ただし呼ぶ時もかっこが必要です。
std::ostream& mycout(bool isOut){ if(isOut){ return std::cout; }else{ return nullout; } } void someThread(bool isOut){ mycout(isOut) << "hogehoge" << std::endl; } //Test code int main(){ someThread(true); someThread(false); return 0; }
関数の中が複雑でないなら直接引数に参照にとってしまうようにもできます。これならかっこも必要ありません。
void someThread(std::ostream& mycout){ mycout << "hogehoge" << std::endl; } //Test code int main(){ someThread(std::cout); someThread(nullout); return 0; }
任意に切り替えのできるクラスを作っておくこともできます。
ストリーム内部のstreambufクラスはrdbuf関数で読み出しや書き込みができるので、これを使って切り替えを実装します。マルチプロセス(MPIとか)のときはグローバル変数が独立しているので、これの実体をグローバルにもっておいて、各プロセスidで初期化しておけば平和になります。
class mycoutClass : std::ostream{ public: mycoutClass() : std::ostream(std::cout.rdbuf()){} void setOut(bool isOut){ if(isOut){ this->rdbuf(std::cout.rdbuf()); }else{ this->rdbuf(nullout.rdbuf()); } } }; mycoutClass mycout; //Test code int main(int argc, char** argv){ int pid; //SOME MPI_INIT FUNCTIONS HERE if(pid == 0){ mycout(true); }else{ mycout(false); } //SOME MAIN FUNCTIONS HERE mycout << "hogehoge" << std::endl; }
これで快適なprint文デバッグ環境が整います。ちなみにファイルへの出力も同じノリで書くことができます。
今までのコードの置換も全部置き換えればいいだけなので比較的楽です。戻すときも単にtrueで初期化してあげれば元の振る舞いに戻ります。
環境非依存なコードかどうかをしっかり吟味していないのでその点は注意して下さい。Linux GCC4.7で確認を行いました。
ピンバック: 最近のブログ記事まとめ | ぞうさんの何でもノート