• 締切済み

コンストラクタでの例外は不可能なのか。

曖昧な記憶なのですが、コントラクタで例外を発生させてはいけないというような記述を見たことがあります。 質問なのですが、 1.そもそも本当にコントラクタは例外を発生させてはいけないのか? 2.なぜコントラクタの例外は問題となるのか? 3.newなどの関数は例外を発生させるが、コントラクタ内でキャッチすれば問題ないのか? 以上、お願いします。

みんなの回答

  • jacta
  • ベストアンサー率26% (845/3158)
回答No.12

> たしかに、臭いものに蓋的な考え方ですが、関数やLibraryから例外を投げられることもあるので、その場合はcatchして適切な処理を与えましょう。みたいな意味合いで書きました。 いや、デストラクタでは、そもそも例外が送出されるかもしれない関数を呼び出すこと自体を避けるべきです。 例えば何かを外部に出力する場合でも、std::ofstreamよりは、決して例外を送出しないstd::fprintfなどを使う方がよいということです。 > コンストラクタは例外を投げない方が良い。 コンストラクタ内で発生したエラーの通知は、例外を送出することで行うべきです。もっと分かりやすくいうと、コンストラクタが失敗した場合は例外を送出した方がよいのです。 > デストラクタは例外を投げてはいけない。 これはその通り。 > メソッドは例外を投げない方が無難。 (メソッドというか)メンバ関数は必要に応じて例外を送出してかまいません。

  • MrBan
  • ベストアンサー率53% (331/615)
回答No.11

御意>#10さん > このようなこともあり、例えばSTL等では例外を投げないことを要求されたりもします。 先にも少し書いたつもりでしたが、STL等は「渡すデータのデストラクタが例外を投げない」ことを "要求" します。 つまり、例外を投げるデータをSTLに渡してはいけない(契約違反)ということです。 それ以外の場合でも、デストラクタからは例外を投げない設計が無難。 理由は既出ですが、タスク例外とかは本当に環境依存の最終手段でしょうし、一般的には投げていい場合の方がかなり限られます。

  • jacta
  • ベストアンサー率26% (845/3158)
回答No.10

以下の2点に関して、もしかすると誤解されている可能性があるので、念のため再確認します。 > 3.デストラクタでの例外でも内部でcatchすれば問題ない。 デストラクタ内部で例外をcatchしても、それだけでは臭いものに蓋をしただけで解決にはなりません。 原則として、デストラクタでは例外を送出しないようにしましょう。 > 4.STLを使用するなら例外を投げない方が無難。 STLというか、標準C++ライブラリや、多くのまともなライブラリは、ユーザー定義クラスのデストラクタから例外が送出されないことを前提にしています。 例外を投げない方が無難なのではなく、「デストラクタから」例外を送出してはいけないということです。

shirousa01
質問者

お礼

回答ありがとうございます。 >> 3.デストラクタでの例外でも内部でcatchすれば問題ない。 たしかに、臭いものに蓋的な考え方ですが、関数やLibraryから例外を投げられることもあるので、その場合はcatchして適切な処理を与えましょう。みたいな意味合いで書きました。 >> 4.STLを使用するなら例外を投げない方が無難。 コンストラクタは例外を投げない方が良い。 デストラクタは例外を投げてはいけない。 メソッドは例外を投げない方が無難。 ということですか?

  • jacta
  • ベストアンサー率26% (845/3158)
回答No.9

デストラクタで止めた例外の行き場に関しては、ひとつだけ裏技的な解法があります。それは、異なるタスクに例外を通知し、そこで例外を処理させる方法です。 もちろん、ここでいう例外の通知方法も処理方法も、C++が言語レベルでサポートする監視ブロックとは異なるものです。 もっとも、そこまでやるのはよほどの状況ですし、ほぼすべての場合でデストラクタからの例外送出は禁止すべきですね。デストラクタからの例外送出は、gotoの1万倍は凶悪です。 最初の質問に戻ると、デストラクタからの例外とは違って、(馬鹿なコンパイラさえ使わなければ)コンストラクタからの例外送出は至って健全です。

shirousa01
質問者

お礼

回答ありがとうございます。 ここまでの意見を見ますと、 1.コンストラクタでの例外はリークやデータの破損に気をつけて使用できる。 2.デストラクタでの例外をオブジェクトの外側に投げることはC++言語の設計上ほとんど不可能。 3.デストラクタでの例外でも内部でcatchすれば問題ない。 4.STLを使用するなら例外を投げない方が無難。 と、言うことですね。

  • MrBan
  • ベストアンサー率53% (331/615)
回答No.8

御意>#7さん 設計上、そもそも投げない仕様か、投げても安全と保証できる用法なら問題はないです。 このような保証が得られない場合に、 uncaught_exception()が入っている方がterminateハンドラよりはマシなんじゃないかと個人的には思いますが、 結局のところは、ここで止めた例外の行き場が問題になる可能性があります。 例えば、例外中に例外が発生するケースだと、 最初の例外に起因しただけで最初の例外さえ正しく処理すれば後の例外は安全に無視できる、などという場合もありえますが、 後の例外が無条件に例外を投げるとterminateのグローバルハンドラで判断するしかなくなってしまいますので、uncaught_exceptionは有効だと思います。 # コンパイラが"正しく"対応していれば。 そうでないような場合(例外は投げるが呼び出し条件の保証もなく、例外中に例外が発生すると不正)が多いことも考えると、 一般則としては「デストラクタで例外を投げてはいけません」に落ち着くと思ってます。

  • jacta
  • ベストアンサー率26% (845/3158)
回答No.7

デストラクタでの例外についてもついでにコメントしておくと... ごくごく限られた状況でしか使用しない(というか使用状況を強制するように設計された)クラスであれば、デストラクタから例外を送出しても問題ないとは思います。 具体的には、関数内で定義されたローカルクラスで、二重例外やコンテナの要素になる可能性を完全に排除できている場合などです。 ちなみに、デストラクタから例外を送出してはいけないからといって、単に例外をcatchして捨てるだけの行為は凶悪ですのでやめましょう。

  • MrBan
  • ベストアンサー率53% (331/615)
回答No.6

> このような処理は、コンストラクタに入れて大丈夫なのでしょうか? コンストラクタ内での例外の場合でも、通常の関数内処理は > // 例外で影響を受けるものの修復等 この部分がキチンとなっていれば大丈夫です。 デストラクタが無い前提で、その時点までの状態をロールバックする。(面倒ですが…。既に指摘があるようにauto_ptrとかは役に立ちます) コンストラクタで特に注意が必要なのは初期化指定で、#3の方が書かれているようなtry blockが必要です。 # これに対応してないコンパイラとかもまだ出回ってます。 ちなみに、初期化処理の場合、  : p1(new B), p2(new C) と書いてあっても、この順番に初期化されるわけではないとか(記述順ではなく、クラスでの定義順に依存する)、はまりどころも満載です。 *** *** *** > また、このように例外が発生することを前提とするコードでも > デストラクタには入れられないのでしょうか? デストラクタ内部で例外が出るのは大丈夫ですが、外に投げたらいけません。  ここでいう「いけません」は、言語レベルでの禁止ではありません。  やることは可能ですが、例えば以下のような危険をはらみます。 例外処理中に例外がおきるとterminateされてしまうので、例外発生時のロールバック中にデストラクタが例外を出すと、プログラムが死にます(terminateのデフォルト処理はabort)。 そして、catchもできません。 なので、とりあえず、 > throw; // これで関数外まで例外を飛ばす これは、uncaught_exceptionで判定してから再投入を決めるか、そもそも「投げない方が無難」です。 # uncaught_exceptionも同様に、まともに動かないコンパイラがまだ出てます。 このようなこともあり、例えばSTL等では例外を投げないことを要求されたりもします。

  • MrBan
  • ベストアンサー率53% (331/615)
回答No.5

> コンストラクタが例外を投げても問題ないということですか? 御意。既に回答があるように、それが本来の正しい(?)用法です。 ただし、例外に対する正しい理解をもつ開発者は少ないです。  C言語にはこのような例外がありませんでしたので、  例えば手探りで移行した人で知識が抜けていたりとか、  C++の入門書でも説明が少なくて知らない人が増えたりとか、  bad_allocすら投げない、try blockも使えない、  でも有名な某コンパイラが幅を利かせたりとかもありますし、  Cでは安全だったコードがC++では例外脆弱だったりとか、  違う言語なので当たり前なのですが認識の無い人も多かったりするのが現実。  Java/C#等の類似言語も、そもそもデストラクタが無かったりして  C++の例外に対する誤った認識を与えてしまうことが多々あります。  # 「尖がったC++」と比べて少し丸い言語たちですから、  # これらの言語自体はそれでいいのですが、C++で同じ感覚だとは問題が…。 コンストラクタに限らず、例外を投げただけで呼びもとの処理が例外脆弱だと問題を起こす可能性があり、このことについてもある程度の配慮は必要であるとは思います。 なお、デストラクタでは例外を発生させてはいけません。 例外安全性が保てなくなります。 曖昧な記憶とのことですので、これと混同されている可能性はあるかと思います。

shirousa01
質問者

お礼

回答ありがとうございます。 私は、たとえばnewで言うなら try{ new char[0x7fffffff]; // ここで例外が発生 } catch(std::bad_alloc val){ // 例外で影響を受けるものの修復等 throw; // これで関数外まで例外を飛ばす } というような処理をよくやります。 このような処理は、コンストラクタに入れて大丈夫なのでしょうか? また、このように例外が発生することを前提とするコードでもデストラクタには入れられないのでしょうか?

  • jacta
  • ベストアンサー率26% (845/3158)
回答No.4

> デストラクタが動かないことを前提にリークに気をつければ、コンストラクタが例外を投げても問題ないということですか? 実際にはリークだけの話ではありません。 引数としてポインタや参照が渡された場合、途中で例外が発生しても、それらの内容を(できれば)破壊すべきではありませんし、静的データメンバ等の状態を変化にも注意しないと、その後の整合性が保てなくなります。

  • jacta
  • ベストアンサー率26% (845/3158)
回答No.3

コンストラクタで例外を発生させてはいけないというのは嘘です。そもそも、オブジェクトの生成に失敗したことを通知するには、返却値のないコンストラクタの場合は例外を送出する以外にありません(errnoのような方法もあるにはありますが...)。 コンストラクタでの例外発生にまつわる問題を回避する最も簡単な方法は、new演算子(または似たような方法でのリソース確保)を複数行わないようにすることです。 A::A() try  : p1(new B), p2(new C) { } catch (std::bad_alloc& e) { } のようにしても、例外はcatchできますが、果たしてp1で例外が発生したのか、p2で例外が発生したのか知るすべがありません。 これだけの問題であれば、auto_ptrを使えば逃げられますが、例外安全に対して理解の浅いプログラマの場合は、ついつい信頼性の低いコードを書いてしまいがちです。 誤解のないように念を押しますが、コンストラクタの内部で発生したエラーの通知には例外を使用すべきです。しかし、同時に例外安全に対する認識と、具体的な対応方法を身に付けることが必須です。

shirousa01
質問者

お礼

回答ありがとうございます。 つまり、デストラクタが動かないことを前提にリークに気をつければ、コンストラクタが例外を投げても問題ないということですか?

関連するQ&A

専門家に質問してみよう