8

人々は、デストラクタからの例外をスローすることにかなり強く反対しています。例としてこの答えを取り上げてください。std::uncaught_exception()他の例外が原因でスタックを巻き戻しているかどうかを移植可能に検出するために使用できるかどうか疑問に思います。

デストラクタに意図的に例外をスローしていることに気づきました。2つの可能なユースケースに言及するには:

  • バッファのフラッシュを伴うリソースのクリーンアップ。これにより、失敗は出力の切り捨てを意味する可能性があります。
  • std::exception_ptr別のスレッドで発生した例外を含む可能性のあるを保持しているオブジェクトの破壊。

これらの例外的な状況を単に無視することは、明らかに間違っていると感じます。また、例外をスローすることにより、デストラクタ自体がに書き込んでいる場合よりも、一部の例外ハンドラがより有用なコンテキスト情報を提供できる可能性がありますstd::cerr。さらに、失敗したすべてのアサーションに対して例外をスローすることは、単体テストアプローチの重要な部分です。その場合、エラーメッセージの後に無視されたエラー条件が続くと機能しません。

だから私の質問は、別の例外が処理されているときを除いて例外をスローしても大丈夫ですか、それともそれをしない理由がありますか?

これをコードに入れるには:

Foo::~Foo() {
  bool success = trySomeCleanupOperation();
  if (!success) {
    if (std::uncaught_exception())
      std::cerr << "Error in destructor: " << errorCode << std::endl;
    else
      throw FooOperationFailed("Error in destructor", errorCode);
  }
}

私の知る限り、これは安全であり、多くの場合、例外をまったくスローしないよりも優れているはずです。しかし、それを確認したいと思います。

4

2 に答える 2

10

ハーブサッターはこの件について書いています:http ://www.gotw.ca/gotw/047.htm

彼の結論は、デストラクタからスローしないことです。スローできない場合に使用するメカニズムを使用して、常にエラーを報告してください。

2つの理由は次のとおりです。

  • 常に機能するとは限りません。時々uncaught_exceptiontrueを返しますが、それでも投げても安全です。
  • 同じエラーを2つの異なる方法で報告するのは悪い設計です。どちらも、ユーザーがエラーについて知りたい場合は説明する必要があります。

特定の再利用可能なコードについては、スタックの巻き戻し中に呼び出されないことを確実に知る方法がないことに注意してください。try/catchコードが何をするにしても、そのコードの一部のユーザーが、例外を処理するための適切な場所にあるデストラクタからコードを呼び出したくないと確信することはできません。uncaught_exceptionしたがって、 「デストラクタから呼び出されてはならない」という関数を文書化する場合を除いて、スローしても安全な場合は常にtrueを返すことに依存することはできません。これに頼った場合、すべての呼び出し元は「デストラクタから呼び出されてはならない」という機能を文書化する必要があり、さらに厄介な制限があります。

他のことは別として、nothrow保証はユーザーにとって価値があります。ユーザーが特定のことをスローしないことを知っている場合、例外に対して安全なコードを作成するのに役立ちます。

1つの方法は、クラスに、失敗した場合にclose呼び出してスローするメンバー関数を与えることです。trySomeCleanupOperation次に、デストラクタはtrySomeCleanupOperationエラーを呼び出してログに記録するか抑制しますが、スローしません。次に、ユーザーはclose操作が成功したかどうかを知りたい場合は電話をかけ、気にしない場合はデストラクタに処理させることができます(以前に例外がスローされたためにデストラクタがスタックの巻き戻しの一部として呼び出された場合を含む) )へのユーザーの呼び出しに到達しますclose。「ああ!」とあなたは言います、「しかし、ユーザーは忘れずに電話をかけなければならないので、それはRAIIの目的を損ないますclose!」。はい、少しですが、問題は、RAIIがあなたが望むすべてを実行できるかどうかではありません。できません。問題は、それが一貫しているかどうかです希望するよりも少ない(失敗した場合に例外をスローする)、またはスタックの巻き戻し中に使用した場合の驚くべきことtrySomeCleanupOperatorではありません。

さらに、失敗したすべてのアサーションに対して例外をスローすることは、単体テストアプローチの重要な部分です。

terminate()これはおそらく間違いです。ユニットテストフレームワークは、をテストの失敗として扱うことができるはずです。スタックの巻き戻し中にアサーションが失敗したとします。確かにそれを記録したいのですが、例外をスローすることによってそれを行うことはできないので、自分自身を隅に追いやったことになります。アサーションが終了した場合、それらを終了として検出できます。

残念ながら、終了すると、残りのテストを実行できなくなります(少なくとも、そのプロセスでは実行できません)。しかし、アサーションが失敗した場合、一般的に言えば、プログラムは未知であり、潜在的に危険な状態にあります。したがって、アサーションに失敗すると、とにかくそのプロセスで他のことを行うことに頼ることはできません。複数のプロセスを使用するようにテストフレームワークを設計することを検討するか、十分に深刻なテストの失敗が残りのテストの実行を妨げることを受け入れることができます。テストフレームワークの外部では、テストの実行には「すべて合格、何か失敗、テストのクラッシュ」という3つの結果が考えられます。テストの実行が完了しなかった場合、それを合格として扱いません。

于 2013-03-05T11:29:28.093 に答える
3

これは、標準がdtorと例外について述べていることです。

15.2

(...)

tryブロックから例外がスローされるポイントまでのパス上に構築された自動オブジェクトのデストラクタを呼び出すプロセスは、「スタックアンワインド」と呼ばれます。スタックの巻き戻し中に呼び出されたデストラクタが例外を除いて終了すると、std :: terminateが呼び出されます(15.5.1)。[注:したがって、デストラクタは通常、例外をキャッチし、デストラクタから伝播させないようにする必要があります。—エンドノート]

非常にあいまいな質問をしたので、答えは次のようになります。

  • アプリケーションがクラッシュしないように、dtorで例外をスローできますか?はい、できます(つまり、コンパイルされ、場合によっては正しく実行されます)。
  • dtorで例外をスローする必要がありますか?いいえ、問題を引き起こす可能性があるため(通常は問題が発生する可能性があります)、すべきではありません。

dtorから例外をスローする必要があるのは、設計が悪いことの表れだと思います。デストラクタで何かをしているようですが、それは他の場所で行う必要があります。

于 2013-03-05T11:18:49.270 に答える