質問: 趣味でC++やっています。 Qtについてなのですが、Qtを使うときは例外は投げることができないのでしょうか? 最初、QListWidgetを継承して "リストのアイテムが選択されたら..."等の挙動を指定できるように QtUse::QtWindow::ListBoxクラス ( QtUse::QtWindow は namespace ) を定義して、 コンストラクタで行う処理を記述したクラスを渡して...という感じでやりました。 で、int QListWidget::getIndex( const QString &text ) というメンバ関数があるとします。 引数として受け取った文字列があるインデックスを返すものです。 このとき、その文字列が QListWidget内にない場合、処理不可能なので std::exceptionを継承した例外クラスを投げました。 すると ランタイムエラーが出ました。 例外を投げなくていい範囲 ( QListWidgetにある文字列 ) にしてみると普通にOK。 情報元は忘れましたが、なんかのサイトで「Qtのスレッド( QThread ) に渡すスレッドから例外を投げるとクラッシュする」というような記事を見た覚えがあります。 Windows APIのスレッド起動も begin_threadでしたっけ? beginが含まれている関数に関数ポインタとして渡すので、 例外が投げられると受け手がいないので例外が補足されず、クラッシュ。 これは理解できます。 なので C由来の方法 ( -1 や 空文字列 等 のようなでたらめな値かどうかをチェックする方法 ) でやるとします。 ここまではまだいいです。 問題はそれ以外の場所から例外を投げる場合です。 QtUse::QtWindow::ListBox等とは別に CMainクラスを定義します。 サンプルコードとかだと QMainWindowを継承して挙動の定義をするクラスです。 ``` // IListBoxEventクラスは ListBoxのイベント処理用のインターフェースクラス。 class CMain : IListBoxEvent, /* ... 他にもイベント処理用インターフェースを継承 */ { public: CMain(){ // 生成等 } ~CMain(){ } // 関数オブジェクト ( ファンクタ ) にするため。 bool opeator()( int argc, char *argv[] ){ init(); uinit(); // 他にもあるが... } bool buttonClicked(){ } bool listItemChecked(int index){ } // 他にもイベント系あり protected: bool init( void ){ // ListBox等の生成や設定 } bool uninit( void ){ // 破棄用 } ... }; int main( int argc, char *argv[] ){ CMain Main; return Main( argc, argv ); } ``` という風にしています。( 厳密には所々違いますが、サンプルなので。 ) で、このCMainに funcメンバ関数 ( 中身はただ単に例外を投げるだけ )を追加して試してみました。 // これを CMainに追加 void func( void ){ throw -1; // 動くかどうかのテストなのでint型を投げてみた.. } このfuncメンバ関数を operator()等で例外処理を伴って記述しても ListBoxのときみたいな Runtime-error が発生します。 Qtには QExceptionという例外クラスがあるようですが、 必ずこれじゃないと無理なのでしょうか? 自分用のライブラリ( CUIでは使えた。例外を投げるクラスや関数がある。 ) を使うことは無理なのでしょうか? QException ( for Qt ) を使えばいいのかもしれませんが、CUI版はできればその実行ファイルさえあればいいようにしたいので... ( Qtを使うとDLLが多すぎる。 "DLL"ディレクトリを実行ファイルと同じディレクトリにおいてそこに入れる...っていうこともできないし... ( また、std::exception から QExceptionに変更するの大変... メンバが違うし... ) もしQExceptionを使わずに例外を発生させてもOKなやり方があればご教授お願い致します。 [情報] 言語: C++ コンパイラ: MinGW Qtバージョン: 5.6 QtCreator: 使用しない ( 設定がわけわからん... ) 宜しくお願い致します。 --- 追記: 例えば、上記でいえばbuttonClickedメンバ関数 ( メッセージを受け取った際に動かすメンバ ) 内で、 ``` try{ func(); // 例外が投げられる可能性がある( 今回は必ず投げますが。 )関数 }catch( int e ){ // ここで表示 } ``` としていますが、なぜかクラッシュします。 投げられた例外の型は同じなのに...
回答: > > 自分用のライブラリ( CUIでは使えた。例外を投げるクラスや関数がある。 ) を使うことは無理なのでしょうか? > > > **ハンドリングする場所を適切に設計**しさえすればどんな例外を発するクラス/関数でも利用できるのではないかと思います。 ``` Qtランタイムのイベントループ |イベント発生 +-------------------->ハンドラー +------>try/catchによるハンドリングの元で +---------->任意の例外が発生する機能を使用 <----------X 例外発生 適切にハンドリングする <--------- <------------------------正常にリターン | |期待外の動作をしたならクラッシュしか選択肢がない ... ``` この例でいえば「イベントループではハンドラーが常に正常リターンすることを期待し、それ以外はクラッシュ」と設計になっていると思います。ゆえにしかるべき箇所(ここではハンドラーの中のどこか)でtry/catchによる適切なハンドリングをすればよく、それを怠ればクラッシュすることに不思議はないという考えです。 Qtであれなんであれプログラムの構造の中で「この場所ではこの例外はこう扱う」というのが明確に設計されているものだと思います。規定されていない例外を「なんでもかんでもなかったことにする」ような設計は多分しないので「そのようなものが起こったら全てクラッシュ」と考える、つまり「デフォルトはクラッシュ」として捉えるのが妥当ではないでしょうか? QExceptionについてリファレンスを見ました そこには異なるスレッドから呼び出し元スレッドへ例外を伝播させたい場合はQExceptionが便利ということが書かれてます。QExceptionの派生にしておくと別のスレッドで例外が発生した場合に呼び出し元スレッドへ例外の内容が伝わるので「どんな例外が起こったか」がわかります。一方QExceptionを用いないと「QUnhandledExceptioin」しか受け取れず、呼び出し先スレッドで何が起こったかは何も分かりません。「なんかの例外がおこったらしい」ということしかわからないわけです。 ``` caller thread(A) ------------|------------------ callee thread(B) | | +---------> | try/catch -----> スレッド間呼び出し ----> <-------------- QException スロー 例外を(A)スレッドへコピーしてスロー <------------ catch((B)からスローされた例外インスタンスのコピーをcatchできる) ... +---------> | try/catch -----> スレッド間呼び出し ----> <-------------- QExceptionでないものをスロー 例外インスタンスは(A)へコピーできない <------------ catch(QUnhandledException(何が起こったかわからない例外しかcatchできない) ``` 単なる推測ですが、QExceptionかどうかと「適切な場所で例外のハンドリングをすべき」ということには直接関係はないと思います。イベントハンドラーでQExceptionをスローしたとき、やはりイベントループでクラッシュする仕様だった・・・となっても驚くにはあたらないと思います。 Qtをよく知らずにコメントしているので間違いがあるかも知れません。その際はご容赦ください。