3

リンクhttp://gotw.ca/gotw/066.htmには、

教訓 #1: コンストラクター関数の try ブロック ハンドラーには、例外を変換するという 1 つの目的しかありません。(そして、ロギングやその他の副作用を行うためかもしれません。) それらは他の目的には役に立ちません。

一方http://www.parashift.com/c++-faq-lite/exceptions.html#faq-17.8

コンストラクターが例外をスローした場合、オブジェクトのデストラクターは実行されません。オブジェクトが元に戻す必要のある処理 (メモリの割り当て、ファイルのオープン、セマフォのロックなど) を既に行っている場合、この "元に戻す必要のある処理" は、オブジェクト内のデータ メンバーによって記憶されている必要があります。

これらの 2 つのステートメントは矛盾していませんか? 最初の 1 つの種類は、コンストラクター内の try キャッチがほとんど役に立たないことを意味しますが、2 番目の種類は、リソースを解放する必要があることを示しています。ここで何が欠けていますか?

4

5 に答える 5

2

それらは異なるものを指します。

最初のものは関数tryブロック、つまりtry関数全体を含むブロックに関連し、コンストラクターの場合は、基本クラスとメンバーオブジェクトのコンストラクターへの呼び出し、つまり実行されるものも含まれます実際のコンストラクタ本体が実行される前。

モラールは、基本クラスまたはクラスメンバーが正しく構築されない場合、オブジェクトの構築は例外で失敗する必要があるということです. したがって、そのようなtryブロックの目的は、そのような例外を変換/再スローし、おそらくインシデントをログに記録することだけでなければなりません。他の方法ではできません:throw内で明示的にしない場合、「半分構築されたオブジェクト」の偶発性を防ぐために、コンパイラによってcatch暗黙が追加されます。throw;

2 つ目は、コンストラクターの本体内で発生した例外を参照します。この場合、tryデストラクタが呼び出されないため、「通常の」ブロックを使用して例外をキャッチし、これまでに割り当てたリソースを解放してから再スローする必要があると言われています。

デストラクタの暗黙の契約は、コンストラクタではない他のメンバー関数と同様に、一貫した状態でオブジェクトに対して動作することを期待しているため、この動作は理にかなっていることに注意してください。ただし、コンストラクターからスローされた例外は、オブジェクトがまだ完全に構築されていないことを意味するため、この契約に違反します。

于 2011-08-18T16:20:49.707 に答える
2

単純な用語へのあいまいな翻訳は次のようになります: function-try-blocks は例外を変換するためにのみ使用でき常に RAII を使用し、各リソースは単一のオブジェクトによって管理される必要があり、それらは矛盾しません。まあ、翻訳は正確にはそうではありませんが、議論は最終的にこれら2つの結論につながります.

C++FAQ lite からの 2 番目の引用では、コンストラクターが完了していないオブジェクトに対してはデストラクタが呼び出されないことが示されています。これは、オブジェクトがリソースを管理している場合、さらに複数のリソースを管理している場合は、深刻な問題に直面していることを意味します。コンストラクターをエスケープする前に例外をキャッチし、取得したリソースを解放しようとすることができますが、そのためには実際に割り当てられたリソースを知る必要があります。

最初の引用は、コンストラクター内の関数 try ブロックがスロー (または再スロー) する必要があることを示しているため、その有用性は非常に制限されており、特に唯一の有用なことは、例外を変換することです。関数 try ブロックの唯一の理由は、初期化リストの実行中に例外をキャッチすることであることに注意してください。

しかし待ってください、関数 try ブロックは最初の問題を処理する方法ではないのでしょうか?

ええと...そうではありません。2 つのポインターを持ち、それらにメモリを格納するクラスを考えてみましょう。そして、2 番目の呼び出しが失敗する可能性があることを考慮してください。その場合、最初のブロックを解放する必要があります。関数の try ブロックを使用するか、通常の try ブロックを使用して、2 つの異なる方法で実装を試みることができます。

// regular try block                       // function try block
struct test {
   type * p;
   type * q; 
   test() : p(), q() {                     test() try : p( new int ), q( new int ) {
      try {
          p = new type;
          q = new type;
      } catch (...) {                      } catch (...) {
          delete p;                            delete p;
          throw;
      }                                    } // exception is rethrown here
   }
   ~test() { delete p; delete q; }
};

最初に単純なケースを分析できます: 通常の try ブロックです。最初のコンストラクターは、2 つのポインターを null (: p(), q()初期化リスト内) に初期化し、両方のオブジェクトのメモリを作成しようとします。2 つのうちの 1 つでnew type例外がスローされ、catch ブロックに入ります。newが失敗しましたか?失敗したのが 2 番目の new であったとしても、それdeleteは実際にリリースされpます。それが最初のものであった場合、初期化リストは最初に両方のポインターを設定し、null ポインターで0安全に呼び出すことができるため、最初の new が失敗した場合でも安全な操作です。deletedelete p

右の例を見てみましょう。リソースの割り当てを初期化リストに移動したため、例外をキャプチャする唯一の方法である関数 try ブロックを使用します。繰り返しますが、ニュースの 1 つが失敗します。2 番目の new が失敗した場合、delete pはそのポインタに割り当てられたリソースを解放します。しかし、失敗したのが最初の new だった場合、 はp初期化されておらず、 への呼び出しdelete pは未定義の動作です。

大まかな翻訳に戻ると、RAIIを使用し、オブジェクトごとに 1 つのリソースのみを使用した場合、型は次のように記述されます。

struct test {
   std::auto_ptr<type> p,q;
   test() : p( new type ), q( new type )
   {}
};

この変更された例では、RAII を使用しているため、例外はあまり気にしません。最初のnewスローの場合、リソースは取得されず、何も起こりません。2 番目のスローが失敗した場合、2 番目のスローが試行される前に完全に構​​築されているpため破棄され(そこにシーケンス ポイントがあります)、リソースは解放されます。pnew

したがって、リソースを管理する必要さえありませんtry...これにより、Sutter が言及した他の使用法、つまり例外の変換が残ります。失敗したコンストラクターからスローする必要がありますが、スローするものを選択できます。initialization_error構造上の内部障害が何であったかに関係なく、カスタムメイドをスローしたいと判断する場合があります。これが、関数 try ブロックを使用できる目的です。

struct test {
   std::auto_ptr<type> p,q;
   test()  try : p( new type ), q( new type ) {
   } catch ( ... ) {
      throw initialization_error();
   }
};
于 2011-08-18T17:04:25.443 に答える
2

教訓#1はfunction-try-blockについて話し、2番目のステートメントは通常の try catch blockについて話しますが、どちらも明らかに異なります。

2 つの文の意味を理解するには、2 つの違いを理解する必要があります。ここでのこの回答はそれを説明しています。

于 2011-08-18T16:17:26.393 に答える
1

まず、コンストラクターの function-try-block は、コンストラクター内の try-catch と同じではありません。

第二に、「元に戻す必要があるものはデータ メンバーに記憶させる必要がある」ということは、「try-catch ブロックを使用して元に戻す」ということと同じではありません。

2 つのステートメントの間に矛盾はありません。異なることについて話しているのです。実際、2 番目のものは、try-catch についてはまったく話していません。

于 2011-08-18T16:21:37.970 に答える
1

これらの 2 つのステートメントは矛盾していませんか?

いいえ。2 つ目は基本的に、コンストラクター (つまり、コンストラクター) から出てくる例外をスローした場合、デストラクタは呼び出されないことを意味します。そして最初のものは、 function-try-block が例外をコンストラクターから出させないことを意味します。コンストラクター自体の内部でキャッチし、その場で処理します。

于 2011-08-18T16:16:36.237 に答える