• 締切済み

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

曖昧な記憶なのですが、コントラクタで例外を発生させてはいけないというような記述を見たことがあります。 質問なのですが、 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

  • C# 例外が発生しないことの保障

    Javaと比較して書きます。 Javaで記述した場合: public class JavaClass {  public static SampleClass s = new SampleClass(); } C#で記述した場合: public class CSharpClass {  public static SampleClass s = new SampleClass(); } JavaでもC#でも、同じコードを記述しているように見えますが、Javaではnew SampleClass()コンストラクタで例外が発生しない事が分かっているのに対して、C#の場合では例外が発生しないとは言い切れません。 これは、Javaでは、例外をスローする可能性のあるメソッド宣言では、その全てについてthrows宣言をしなければいけないのに対して、C#にはこの制約が存在しないことが原因です。 これについて何が困るかといいますと、C#で、static宣言な変数や静的コンストラクタで安易にメソッド呼び出し等を行うと、キャッチできなくなってしまいます。 public class Exceptionner {  public Exceptionner()  {   throw new ApplicationException("Exceptionnerクラスの例外");  } } public class SampleClass {  public static Exceptionner e = new Exceptionner(); // ここで例外が発生するが、キャッチできない。 } public class MyEntryPoint {  public static void Main()  {   try   {    SampleClass s = new SampleClass();   }   catch (Exception e)   {    // System.ApplicationExceptionでなくSystem.TypeInitializationExceptionとなる。    // つまり、元の例外の情報は失われている    Console.WriteLine(e.GetType());   }  } } これを現在漠然と問題視していますが、何かよい解決策はありませんでしょうか。 望んでいる解決策: ・C#でもメソッドが例外を返さないという保障がソースレベルでメソッドやコンストラクタに宣言可能? ・C#では例外をちゃんとキャッチしなくてもスマートに記述することが可能? ・問題視する必要がない?(whyも含めて)

  • コンストラクタやデストラクタと例外について

    対処法が「概念的に」書かれているサイトや書籍は結構あるようなのですが、具体的なコードでビシッと書かれてることや、そもそも根本的に「どういう場合に例外が発生するのか」の、帰納的な説明はなかなか見つからないので、もやもやしています。 また、「人が書いたコードを使う場合に全部読んでられないような状況」という前提が暗黙にあって書かれている文章が多いと感じます。 しかし、根本的なところが分からないともやもや感は拭えません。 1.もし仮に自分が全てのコードを把握し、「コーディングの段階で明白に消せる例外の可能性」を、あらかじめ消しておける(さすがにnewなどC++の標準的機能は使用し)とした場合は 自分自身で例外をthrowするか、メモリ不足でbad_allocになる以外にコンストラクタ(あるいはデストラクタ)でcatch出来るような例外が発生する可能性はあるのでしょうか? そして例えば以下のように書いたとします。(必要なヘッダ等は省略してあります) ///////////////////ヘッダ側//////////////////////// class A{ double d; public: A(double a=0) : d(a) {} ~A(void){} }; class B{ std::auto_ptr<A> p; A *alist[10]; public: B(void); ~B(void); }; ///////////////////こちらがソースです//////////////////////// B::B(void) try { memset(&alist,0,sizeof alist); std::auto_ptr<A> ap( new A(.8) ); p=ap; for ( int i=10;i; ) alist[--i]=new A; } catch(std::bad_alloc){ for ( int i=10;i; ) delete alist[--i]; } catch(...) { /*......*/ } B::~B(void){ try{ for ( int i=10;i; ) delete alist[--i]; }catch(...){} } こうしたとすると 2.このコードはコンストラクタ中で例外が発生した場合、コンストラクタなので「呼ばれることになった原因の場所へ自動的に例外が再度投げられる」という点を除けば、安全でしょうか? 3.そもそもコンストラクタで例外が発生した場合、呼び出し元に届くように「自動的に、あるいは手動で」投げなければ「ならない」のでしょうか? 4. 1とかぶりますが「このコードの場合に限定」していうと、catch(std::bad_alloc)の後ろに書いたように、果たしてcatch(...)は必要なのでしょうか? つまりstd::bad_alloc以外の例外が発生する可能性はありますか? 5.このコードの場合では、このようにデストラクタをtryブロックで囲う必要は事実上ないと考えて良いですか? 6.また、もしclass Aのデストラクタを見ることができないという場合は、逆に囲うべき、という意味なのでしょうか? 7.auto_ptrのかわりに通常のポインタを使う場合 ポインタの配列alistと同じように、先にNULLにしておいてからnewして、catch(std::bad_alloc)に入った場合は delete ポインタ。 例外が発生しなかった場合デストラクタのほうにも解放処理はちゃんと書く、としていれば、この場合大丈夫でしょうか?

  • 例外処理がまったくわかりません!!

    下のプログラミングについていくつか質問があります。 (1)etest()メソッドってなんですか? (2)存在しないファイルを開くってどういうことですか? (3)FileReader( )対応catch節、Close( )対応catch節ってなんですか? (4)あと、プログラミングあとの本の解説の部分がよくわかりません。抜粋してみます。 「一般的な例外クラスはxxxExceptionというクラス名をしています。このタイプの例外クラスはみな「Exception例外クラス」をスーパークラスにしています。この場合、「catch(Exception e)」を使うとすべてのxxxException例外を補足できるという仕様になっています。ですから下のetest( )メソッドは次のように、ひとつのcatch節で記述することができます。このようにしても表示される4エラーメッセージ(eで指示)は、正しく発生例外(FileNotFoundExceptionまたはIOException) 対応のものになります。」 public static void etest( ) { try { // try節を書く FileReader fr = new FileReader("xfile.txt"); } catch (FileNotFoundException e) { // FileReader( )対応catch節 System.out.println("例外1: " + e); // 例外情報を表示 return; // 戻る } catch (IOException e) { // Close( )対応catch節 System.out.println("例外2: " + e); // 例外情報を表示 return; // 戻る } System.out.println("ファイルは正しくオープンされました"); } } よろしくお願いします。

    • ベストアンサー
    • Java
  • C# の例外処理について

    こんにちは。 C#の例外処理について質問させてください。 ある関数の内部において、一般的な例外をユーザー定義の例外に変えて投げ、 この関数の利用者には必ずそのユーザー定義の例外を処理するよう強制したいと思っています。 しかし、C#で関数の利用者に例外処理を強制させる方法が見つかりません。 そもそも、関数の利用者からは、その関数でどのようなユーザー定義の例外が投げられるかすらわからないように思います。 正しくcatchするには関数の中身を追うしかない?と疑問に思っています。 C#ではこのような場合、どうするのが一般的なのでしょうか? Javaでは例外処理の強制は簡単なので、構文が似てるだけに不思議に思っています。 基本的な仕様の見落としだったら申し訳ありませんが、よろしくお願いいたします。

  • C++の例外処理について

    C++にjavaのような例外処理ができるのを知りました 構文は try { stat1 } catch(except-decl) { stat2 } finally { stat3 } stat4 ただし stat1:例外が発生する可能性があるステートメント stat2:例外が発生したときに実行するステートメント stat3:例外が発生しなくても実行するステートメント stat4:ステートメント except-decl:捕獲する例外の宣言 です ここで質問です (1) 冷害が発生したときstat4は実行されるのでしょうか? されない場合にはその後どう言う処理がなされるのでしょうか? (2) except-declはどうのような記述がなされるのでしょうか? (4) 例外を発生させる構文 throw except (except:発生させる冷害) のexceptの部分にはどのような記述がなされるのでしょうか? よろしくお願いします

  • close()で例外が投げられる理由

    最近、Java を勉強しはじめておるのですが、ファイル入出力処理で例えば、out.txt なるファイルを出力するサンプルとして FileWriter out = null; try {  wt = new FileWriter("out.txt");  out.write("はろー"); } catch ( IOException e ) {  e.printStackTrace(); } finally {  try {   if (out != null ) out.close();  } catch (IOException e) {   e.printStackTrace();  } } という風に、FileWriter オブジェクト out をクローズする時に IOException 例外が投げられる可能性に対処するため、try / catch でこれをつかまえるように、と聞きました。 そして驚くべきことに、close() が例外を投げた時の catch 節には特に書くべき処理はない、というのです。 そこで質問です。 1. 本当に FileWriter#close() の IOException 例外に対処するためのコードは不要なのですか?(常に握り潰しているだけ?) 2. 対処するコードが不要なら、そもそも FileWriter#close() が IOException 例外を投げる必要がないのではないですか? 長文失礼しました。

  • どの例外でcatchすればいいのですか?

    例外の使い方を理解できていません。 さっき検索してみて下の質問と回答を見つけました。 例外処理がまったくわかりません!! http://www.okweb.ne.jp/kotaeru.php3?q=704380 この中の#2さんの回答、 >    catch (FileNotFoundException e){ ・・・ } >     catch (IOException e){ ・・・ } >   などと分けて書かなくとも、 >     catch (Exception e){ ・・・ } >   と書けば、すべての例外を処理できます。 を読むと「それなら、どんな例外でもすべて"(Exception e)"でcatchすればいいじゃん」と思うのですが駄目ですか? もし駄目ならどんな基準でExceptionを選んでいるのですか? 例えば、下のような二分検索木への挿入メソッドの場合、 どこでどのような例外を使ってcatchすればいいのですか? 私の思うような例外を入れてみましたが、どんなエラーが出るかも分からないし…。 だからと言ってthrow ~ catchを入れないとJBuilderが怒るんですよね…。 public void insert(int el) { IntBSTNode p = root, prev = null; try { while (p != null) { prev = p; if (p.key < el) p = p.right; else p = p.left; } if (root == null) root = new IntBSTNode(el); else if (prev.key < el) prev.right = new IntBSTNode(el); else prev.left = new IntBSTNode(el); } catch (Exception x) { System.out.println("エラーだ! 何か表示"); } } 分かる方、説明をお願いします。

    • ベストアンサー
    • Java
  • phpの例外処理がエラーをトラップしてくれない。

    以下のように例外処理を記述しした場合、 try{ //ここでエラー } catch (Exception $e){ $ErrorMsg="その他エラー:".$e->getMessage(); } tryの中で、0除算によるエラーが発生してもエラーをトラップして、例外 処理のcatchに飛びません。 どのように記述すればエラーをトラップできるのでしょか?

    • ベストアンサー
    • PHP
  • コンストラクタでnewを失敗した場合の対応について

    よろしくお願いします。 クラスのメンバで3つのlongのポインタを宣言し、 コンストラクタ内でそれぞれにnewして領域を確保 しています。 質問1)newを失敗した場合には、そのポインタに     対してdeleteしてはいけないのでしょうか? 質問2)上記3つもエリアの確保のうち、2つめで失    敗した場合、1つ目のdeleteをしてやる     必要はあるのでしょうか? catch(bad_alloc)でその処理をしようとおもうのですが、そもそもコンストラクタで例外を発生させるなと かかれている書籍もあるようです。ただ、すでにそういう記載になってしまっており、できれば、いまの構造でメモリーリークを防げないかと思案しております。どなたか、よい方法をご存知の方いらっしゃいましたら、アドバイスいただけましたら幸いでございます。

  • 例外処理に関して

    質問なのですが、よろしくお願い致します。 ファイルに空白行があった場合、任意に例外を発生させることをしているのですが、 (例外処理内容:ファイルにログを出力する) 例外が2回キャッチされているようで、2回目にファイル(ログファイル)は既に開かれています。というエラーが出ますが、以下のソースでは、呼出元と呼び出されたメソッドともに例外を処理してしまうのでしょうか? また、どう改善したらいいでしょうか? ご教授よろしくお願い致します。 呼出元 public Sub Timer1_Tick(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Timer1.Tick Try    Dim cls As New testClass cls.timer1() Catch e as Exception 'ログファイル出力 End Try End Sub 呼び出されるメソッド Public Sub Timer1() try FileOpen(1,"ファイルパス",OpenMode.Input) if Trim(LineInput(1)) = "" Then FileClose(1) Throw New Exception End If Catch e as Exception    'ログファイル出力 End Try End Sub