C++には例外処理というものがあります。try〜catch構文の事です。これ自体の使い方は色々と参考書に書いてあると思いますので省略。
とある関数で、エラーが起きたらその事を呼び出し元に通知したい時、エラー値をreturnするか、それとも例外をthrowするか、
どちらが良いのでしょうか?もちろん答えはcase by caseです。どちらの方法にも利点欠点があるので、
状況に応じて使いましょう。では、どんな状況ではどちらが有利なのかを考えてみましょう。
エラー値をreturnする方法は、とにかくお手軽にエラーを返せる利点です。ですが、返せるエラー情報はたかが知れています。 もちろん、グローバルのエラー用文字列変数を用意しておく等色々情報を返す事は出来ますが、 例外をthrowする方法に比べて「エラー情報の量」という点において弱いです。
例外をthrowする方法は、様々なエラー情報を返せますが、受け取る方にそれ相応の準備が必要です。 try〜catchの外でその関数を呼んでしまうと、一気にアプリケーションが停止してしまう可能性もあります。 throwする方のコードを書く際にも、何故そのエラーが発生したのかを考えてthrowする例外を決める必要があるので、 結構面倒です。ですが、関数の中の関数から、たった一つのthrowでtry〜catchがある所まで戻ってこれるので、 関数呼び出しの階層が深い場合は結構有効です。
双方の利点と欠点を考えてみましたが、ではどんな場合にどちらが有効なのでしょうか。
私の場合は、「ユーザーの入力」を処理する場合のエラーでは例外をthrowします。
(その後は大概エラーメッセージを表示します)
ユーザーの入力にほとんど頼らない関数の成功及び失敗には、エラー値をreturnします。
つまり、その関数に失敗した場合、プログラムで対応する場合にはエラー値を返し、
ユーザーが対応する場合には、例外をthrowして正確なエラーメッセージを返すわけです。
この場合のユーザーは、プログラムの外から操作をする人、という意味です。
ライブラリ等を作る場合には、ライブラリを作る人がユーザーになるわけですが、
こういった場合は私は平気でエラー値をreturnします。
要はプログラム作成者がある程度の知識を持っている事を前提としているわけですね。
なにより、例外をthrowするようなライブラリは、使うのが結構面倒だったりします。
例外クラスも必要となるので、クラス数が増えてライブラリが複雑になりますし・・・
まあ、これは所詮は私の意見です。一つのプログラム中ではエラーの返し方は統一するべきだ、
という方針もあるかもしれません。ライブラリでも、ものによっては例外をthrowした方が
良いかもしれません。結局の所、case by caseですね。(最初の結論に戻ってしまった。)
この意見が、もしどなたかの書いているコードがどのcaseであるか、というの事の判断の助けになりましたら幸いです。
なお、これは日記です。一般的な日記の例に漏れず、これもあまり深く考えて書いているわけではないです。ご注意を!
モーダルダイアログ表示直後になにかしたい、と思うことは良くあります。
例えば、簡単な解凍ソフトを見てみると、ダイアログが出てきて、メーターがびーと0%から100%まで上がって、
解凍完了、というものもあります。
これと同じ事をしたいと思った場合はどうすれば良いのだろうか?と思って
色々試して見ました。
表示前に何かする、という方法は山ほどあるのですが、表示後に何かする方法って
簡単では無いようです。なにしろ、表示完了のメッセージというのは送られて来ません。
WM_CREATE
も、WM_SHOWWINDOW
も、表示前に送られてくるメッセージです。
ですが、表示前のメッセージでもうまく利用すれば良いのです。
WM_SHOWWINDOW
のメッセージハンドラを追加して、以下のコードを追加しましょう。
-----
なにか突然色々な変数が出てきましたね。一つずつ説明していきますと、
if (bShow && !m_bPosted){
m_bPosted = TRUE;
PostMessage(WM_COMMAND, MAKEWPARAM(ID_START, 0), NULL);
}
-----
bShow
は、
WM_SHOWWINDOW
のメッセージハンドラOnShowWindow
の第1引数のはずです。
もし違っていたら変えておいて下さい。
m_bPosted
は、ダイアログクラスにBOOL型(boolでも良いですが)のメンバ変数として
追加しておいてください。そして、コンストラクタでFALSEに初期化して下さい。
OnShowWindow
は何度も呼ばれる可能性があるので、
一番最初を判定するために、この変数が必要となります。
PostMessage(...);
は、コマンドメッセージを送ります。
9/15日の日記の方法を使いました。
メニューからID_STARTを押したのと同じ効果があります。他のコマンドメッセージを送る時は、
ID_START
を別のIDに変えて下さい。
実は、今月からこの日記をテキストエディタで書いています。以前はNetscape4.6のComposerを使っていました。
最近Netscape6を導入したので、次はNetscape6のComposerでも使おうと思ったら、前のComposerと比べて
表示が全然違うのです。前は無かったところに改行が入っていたり、前は改行があった場所が無かったり、と・・・。
これにこりたのでテキストエディタを使う事にしました。
これならば、別のエディタに変えても大差は無いですしね。
ちなみにNetscape6の使い心地はあまり良くないです。重い上に、バグが凄まじく多いです。
基本的な機能にもバグが大量にあります。なんでも、どんなOSでも表示が同じになるように、
Netscape独自のインターフェースを導入したとかなんとか・・・。
そして、このインターフェースが、Windows標準のインターフェースよりも
重く、かつ機能面でも劣っています。Netscape4.6の方がずっと軽くて良かったなぁ・・・。
前回の続きのようなものです。MFCのモーダルダイアログ中で長い処理をする場合に
このメッセージループを使うと便利です。
前回のMFCのメッセージループとは何が違うか、と言いますと、
モーダルダイアログは、長い処理中に、閉じるボタンやOKボタンを押されてしまう
時があります。こういった時は、長い処理を中断して、ダイアログを閉じるべきでしょう。
つまり、ダイアログが閉じたかどうかの判定が加わるわけです。
このダイアログが閉じた判定は、IDOK
やIDCANCEL
コマンドが送られてきた判定とは違います。
OnOK
やOnCancel
、はたまた別のどこかの関数で、EndDialog
が呼ばれたことを判定しなくてはなりません。
そのための関数がContinueModal関数です。
では、実際にメッセージループのコードを書いてみます。
-----
・・・結局MFCのメッセージループと大差無いですね。長々と説明したわりにはたった1行追加されただけです。
なお、上の2つの
while (::PeekMessage(&msg, NULL, 0, 0, PM_NOREMOVE)){
if (!AfxGetThread()->PumpMessage()){
AfxPostQuitMessage(msg.wParam);
return;
}
if (!ContinueModal())return;
}
-----
return
は、メインのメッセージループへ戻るためのreturn
です。
このメッセージループがメッセージハンドラに直接書いてある場合は、ただreturn
するだけで
良いですが、ハンドラからさらに呼ばれた関数に書いてあるような場合には、臨機応変に対応して下さい。
いつのまにか12月も半ばになってしまいました。開発の方はゲームに忙しくて遅々として進みません。 いや実は勉強に忙しいのかもしれませんが。
最近Javaの開発をやっていて思うのですが、今の開発ツール(VisualCafe3.0c)を用いている限り、 Javaのデバッグモードはめちゃめちゃ思いです。 ただでさえ重たいJavaが、10倍以上遅くなっています。 正直実用にならなかったので、コードのあちらこちらでデバッグメッセージを 出力してデバッグしています。 こうしてみると、VC++のデバッグモードは恐ろしく強力です。 かなり高速に動き、ブレークポイントを指定した場所で止まって、1行ずつ簡単にトレース! MFCの内部でもばっちりトレースできます。みなさんデバッグモードはどんどん使いましょう。 これだけ強力なデバッガが使えるというのは昔に比べると天国です。 でも何年か経てば、あの頃のデバッガは貧弱だったな〜と言っているのかもしれませんね〜。
今回のトピックはメッセージループについてです。メッセージループは、イベントドリブン型の
アプリケーションのまさしくメインループとなるループです。ウィンドウやスレッドに対して
送られてきたメッセージを全て受け取るループです。
ですが、今回のトピックのメッセージループは、このメインとなるメッセージループではなく、
長い処理を行う時に使う物です。まあ実体は同じ物なんですが。
時々、長い長い処理を行いたいと思うときがあります。例えば、Func()という関数を10000回呼ぶことにしましょう。
Funcはそれなりに時間がかかる処理を行うものとします。
こういった時、for (int i = 0; i < 10000; i++)Func();
と真面目に書くと、結構時間がかかります。いや、時間がかかること自体は問題ありません。
これはどうしても時間がかかってしまう処理なのですから。
ですが、この処理中、ウィンドウに対する再描画や、ウィンドウの移動等が何も行われなくなります。
ユーザーから見ると、まさしくアプリケーションがハングアップしてしまったように見えるでしょう。
これは、メインループまで処理が戻らなくなってしまったために発生する現象なのですが、
これを避けるにはどうすれば良いのでしょうか。そのための方法としては、スレッドを使う方法と、
ループ内にメッセージループを入れる方法があります。今回は後者について述べたいと思います。
SDKのメッセージループは簡単な構造になっています。例のFunc()を10000回呼ぶコードだと次のようになります。
-----
for (int i = 0; i < 10000; i++){
Func();
MSG msg;
while(::PeekMessage(&msg, NULL, 0, 0, PM_NOREMOVE)){
if (::GetMessage(&msg, NULL, 0, 0))アプリケーション終了;
::TranslateMessage(&msg);
::DispatchMessage(&msg);
}
}
-----
::GetMessage
は使わず、::PeekMessage
だけでも出来るのですが、
アプリケーション終了判定をするために態々::GetMessage
は使わず、::PeekMessage
を使ってみました。
これで、このループ中にもメッセージの処理が出来ます。なお、メッセージを処理した結果再びこの
長い処理が開始されてしまうような事は避けて下さい。
例えば、ボタンを押したら長い処理を開始するような場合は、長い処理が終了するまで、そのボタンをEnableWindow
あたりを呼んで、押せないようにしておいて下さい。
Func()
で処理を行っている最中ではもちろんメッセージは処理されていません。
ですので、Func()
内でも長い処理をやっている場合は、メッセージループの場所を、Func()
内に
移して下さい。
SDKの方のメッセージループは、かなり多くの方が知っていると思います。
知っている人がより少ないと思われるこちらのトピックが今回のメインだったりします。
SDKのメッセージループをMFCで使っても、さほど問題が無いように感じるかもしれませんが、
実はとても大きな問題が一つあります。それは、PreTranslateMessage
が
呼ばれない事です。これを使っていない方はまず問題はありませんが、
重要な処理をしている場合では、上のSDKのメッセージループでは問題が発生するかもしれません。
よって、MFCらしくメッセージループを改良してみました。
-----
ここの
MSG msg;
while (::PeekMessage(&msg, NULL, 0, 0, PM_NOREMOVE)){
if (!AfxGetThread()->PumpMessage()){
AfxPostQuitMessage(msg.wParam);
return;
}
}
-----
return
は、メインループに帰る為のreturn
です。
つまり、QUITメッセージを受け取ったら、再びQUITメッセージを自分自身に送り、
後はMFCに任せるのです。恐らくMFCは適切に処理してくれるでしょう。
PumpMessage
は、MFCでのメッセージ処理です。
しっかりとPreTranslateMessage
を呼び出してくれます。
ここらへんの関数は、MSDNに載ってませんので、もしかしたら次のバージョンのMFCでは
すぱーんと削除されてしまってしまうかもしれませんので気をつけて下さい。